• 1. EJB3.0 JPA 概述
  • 2. 什么是JPA?Java Persistence API(EJB3 Entity Bean) 在Java EE5中, Entity Bean做为EJB规范中负责持久化的组件将逐渐成为一个历史名词了,作为J2EE 4规范中最为人所熟悉的Entity Bean在Java EE5中被推到重来,取而代之的是java开发的通用持久化规范Java Persistence API 1.0, 其实就是完全重新定义了的Entity Bean规范(目前在很多场合中,由于历史原因我们仍然使用ejb3持久化来称呼这个规范)。JPA作为java中负责关系数据持久化的组件已经完全独立出来成为一个单独的规范,而不再属于Enterprise Java Bean的范畴(EJB更多的是指Stateless/Stateful session bean和Message Driven Bean)。 值得注意的是Java Persistence API并不是J2EE环境专用,而是在java中的通用API。意味着我们可以在任何需要访问关系数据库的地方使用JPA,甚至包括swing开发的桌面应用。JPA也不要求一定在J2EE容器中才能运行,而是任何有JVM的环境都可以运用。 这就使得我们可以很容易的把JPA作为一个持久化组件自由的和各种容器/框架(EJB3容器, Spring等等)组合。  2
  • 3. 什么是JPA?JPA的生命周期 分离状态自由状态持久状态删除状态3
  • 4. 持久化实体管理器EntityManagerEntityManager 在Java Persistence(持久)规范中,EntityManager是为持久化操作提供服务的中枢。实体作为普通java对象,只有在调用EntityManager将其持久化后才会变成持久对象。EntityManager对象在一组固定的实体类与底层数据源之间进行O/R映射的管理。它可以用来管理和更新Entity Bean,根椐主键查找Entity Bean,还可以通过JPQL语句查询实体。当EntityManager注入到EJB容器中,EJB容器会对EntityManager对象所依赖的持久上下文环境具有完全的控制权。 实体的状态: 新建状态: 新创建的对象,尚未拥有持久性主键。 托管状态:已经拥有持久性主键并和持久化建立了上下文环境 游离状态:拥有持久化主键,但是没有与持久化建立上下文环境 删除状态: 拥有持久化主键,已经和持久化建立上下文环境,但是从数据库 中删除。4
  • 5. 持久化实体管理器EntityManagerEntity获得find()或getReference() 如果知道Entity的主键,可以通过find()或getReference()方法获得。方法的第一个参数为实体类(class类型),第二个参数为Entity的OID(OID为类标识符,也就是@ID指定的属性值)find()方法返回指定的OID实体,如果这个实体存在于当前的持久化环境, 则返回一个被缓存的对象,否则会创建一个新的Entity,并加载数据库中相关信息, 同时OID不存在于数据库中,则返回一个null。getReference()方法与find()方法类似,不同的是:如果缓存中不在存指定的Entity, EntityManager会创建一个Entity类的代理,但是不会立即加载数据库中的信息,只有 第一次真正使用此Entity的属性才加载,所以如果此OID在数据库不存在,getReference()不会返回null值,而是抛出EntityNotFoundException5
  • 6. 演示案例在上一节的案例基础之上,在StuInfoManger接口中定义一个public StuInfo getStuInfo(Integer id) 进行数据的查找./** * 根据ID获得StuInFo对象 * @return */ public StuInfo getStuInfo(Integer id){ StuInfo sInfo = new StuInfo(); sInfo = em.find(StuInfo.class, id); return sInfo; }测试代码: InitialContext ctx=new InitialContext(); StuInfoManger stm = (StuInfoManger)ctx.lookup("StuInfoManagerBean/remote"); //测试查找数据 StuInfo sInfo = stm.getStuInfo(3); System.out.println(sInfo.getStuname()+"的地址是:"+sInfo.getStuaddress());6
  • 7. 演示案例当我们要使用getReference()方法的时候,我们获取的对象只是一个代理对象,这个时候我们需要在方法中使用一个对象才能实现代理对象转向实例对象。public StuInfo getStuInfoRe(Integer id){ StuInfo sInfo = new StuInfo(); try { sInfo = em.getReference(StuInfo.class, id); //此处我们需要使用任何一种方式,但只需要引用sInfo对象即可 System.out.println(sInfo);//此处我们可以模拟游离状态 } catch (EntityNotFoundException notFound) { // TODO: handle exception } return sInfo; }//测试一下: StuInfo sInfo = new StuInfo(); sInfo =stm.getStuInfoRe(2100); System.out.println(sInfo.getStuname()+"的地址是:"+sInfo.getStuaddress());7
  • 8. 持久化实体管理器EntityManager持久化实体persist() persist()用于将新创建的Entity纳入到EntityManager的管理。该方法执行后,传入persist()方法的Entity对象转换成托管状态。如果传入persist()方法的Entity对象已经处于托管状态,则persist()方法什么事情都不做。如果对删除状态的Entity进行persist()操作,会转换为托管状态。如果对游离状态的实体执行persist()操作,可能会在persist()方法抛出EntityExistException(也有可能是在flush或事务提交后抛出)。 8
  • 9. 持久化实体管理器EntityManager合并实体merge() merge()用于处理Entity的同步。即数据库的插入和更新操作,因为Entity对象有可能被序列化传递到客户端,然后在客户端修改了相关的属性后再将Entity对象传输到服务器端。在服务端需要将这个游离的实体纳入到EntityManager的管理中,从而使数据同步到数据库中。对于不同状态的Entity对象 调用merge()方法具有以下行为: 如果是新建实体,则会创建该Entity的一份拷贝纳入到EntiytManager中, 所以此特性也可以用于保存数据 如果是托管状态,merge()操作会被忽略 如果是删除状态,merge()操作会抛出IllegalumentException 如果是游离状态,则执行规则如下: a.如果此时容器中存在相同主键的实体B,则会将传进来的实体参数 copy到对象B,容器会将B对象中的属性同步到数据库。 b.如果容器中不存相同的主键的实体,容器则会创建该实体的一份copy 对象B,然后将对象B纳入到EntityManager管理中。9
  • 10. 演示案例增加一修改方法public void updateStuInfo(StuInfo stuInfo){ em.merge(stuInfo); }1、测试方法一:采用对原数据进行修改。 测试一下,可以采用修改之前和修改之后的数据进行对比.2、测试方法二:对新数据进行添加保存 StuInfo stuInfo = new StuInfo(); stuInfo.setStuname("zhangsan"); stuInfo.setStuaddress("株洲"); stm.updateStuInfo(stuInfo);10
  • 11. 持久化实体管理器EntityManager删除remove() 需要删除某个实体对象,也就是删除数据库中某条记录。 当remove()方法是在事务外调用,则会抛出TransactionRequriedException。 对于处于不同状态的实体,执行remove()方法具有以下行为: 如果是新建状态,则此操作会被忽略 如果是托管状态,则此实体会被删除 如果是删除状态,则此操作会被忽略 如果是游离状态,那么会导致IllegalArgumentException。11
  • 12. 演示案例定义一个删除方法:public void removeStuInfo(Integer id){ StuInfo sInfo = this.getStuInfo(id); em.remove(sInfo); }这个时候,我们可以模拟游离状态。改写修改方法 public void removeStuInfo(StuInfo sInfo){ em.remove(sInfo); } //测试代码这样写,我们则会得到IllegalArgumentException异常情况 StuInfo sInfo = new StuInfo(); sInfo =stm.getStuInfoRe(2100); stm.removeStuInfo(sInfo); System.out.println(sInfo.getStuname()+"的地址是:"+sInfo.getStuaddress());12
  • 13. 持久化实体管理器EntityManager执行JPQL操作createQuery() 可以通过使用JPQL查询语句来查询指定的Entity。要执行JPQL需要创建一个Query对象,可以通过使用createQuery()方法创建。13
  • 14. 注意1、 createQuery()该查询语句是查询实体,所以,select * from StuInfo中的StuInfo是实体对象的名字。 2、Query 对象为import javax.persistence.Query;/** * 根据Query查询 */ public void getStuInfoAllByQuery(){ Query query = em.createQuery("select s from StuInfo s where s.stuaddress like '%changsha%'"); List list = query.getResultList(); Iterator iterator = list.iterator(); while(iterator.hasNext()){ StuInfo stuInfo = (StuInfo)iterator.next(); System.out.println(stuInfo.getStuname()+" "+stuInfo.getStuaddress()); } }14
  • 15. 持久化实体管理器EntityManager执行SQL操作createNativeQuery() 如果要执行SQL语句,则可以使用createNativeQuery()方法。注意在这里使用的SQL语句,而不是JPQL语句。执行数据库中的SQL语句15
  • 16. 持久化实体管理器EntityManager刷新实体refresh() 如果你怀疑当前托管对象存放的并非数据库最新数据,(比如:在你执行了find()方法之后,有人悄悄在数据库中修改了数据),可以通过使用refresh()方法刷新体。 如在实体在不同状态,调用refresh()方法,会具有以下行为: 如果是新建状态,则此操作那么会导致IllegalArgumentException 如果是托管状态,则此实体会刷新实体对象 如果是删除状态,则此操作那么会导致IllegalArgumentException 如果是游离状态,那么会导致IllegalArgumentException16
  • 17. 利用JPA完成IDUQ的功能完整案例:StuInfoManagerBean.java 小结:EntityManager实现操作的过程。17
  • 18. 关系与对象映射双向一对多及多对一映射 当one的一方存在与many一方关系的定义,而many也存在one一方关系的定义时,叫做双向关系。代码中体现在one的一方存在一个集合属性指向many一方,而many一方也存一个属性指向one的一方。订单表订单明细表18
  • 19. 关系与对象映射双向一对多及多对一映射 双向一对多关系中,必顺存在一个关系维护端,在JPA规范中,要求 many的一方作为关系的维护端(owner side),one的一方作为被维护端(inverse side)。我们可以在one方指定@OneToMany注释并设置mappedBy属性,以指定它是这一关联中的被维护端,many为维护端。在one一方维护一对多的关系Public @interface OneToMany{ CascadeType[] cascade() default {} FetchType fetch() default FetchType.LAZY String mappedBy() default "" Class targetEntity() default void.class }指定要关联的Entity对象,一般不需要设定指定实体关系中的cascade动作CascadeType.PERSIST CascadeType.REMOVE CascadeType.REFRESH CascadeType.MERGE CascadeType.ALL Fetch用于指定属性在首次加载实体是采用即时加载或延时加载.fetch的值在 FetchType.LAZY:设定为延迟加载(默认值) FetchType.EAGER:设定为即时加载用于指向被关联实体中的指向 自已的属性成员名称19
  • 20. 关系与对象映射双向一对多及多对一映射 在many中也需要设置与one的一方进行关联。Public @interface ManyToOne{ CascadeType[] cascade() default {} FetchType fetch() default FetchType.LAZY boolean optional() default true Class targetEntity() }含义与OneToMany一样设定关联方是否允许为null值, 当值设为true时,允许外键字段为null, 当值设为false时,不允许外键字段为null用于指向与在One中包含自已的属性名称指向多表中的外键字段20
  • 21. 演示案例Orders//Orders表实体 @Entity @Table(name="orders",schema="t27") public class Orders implements Serializable{ private Integer orderid ; private Float amount; //在单的一方,用一个Set进行概括. private Set orderSet = new HashSet(); …. @OneToMany(mappedBy="orders",cascade=CascadeType.ALL,fetch=FetchType.EAGER,targetEntity=OrderItem.class) public Set getOrderSet() { return orderSet; } public void setOrderSet(Set orderSet) { this.orderSet = orderSet; }21
  • 22. 演示案例OrdersItem@Entity @Table(name="orderitem",schema="t27") public class OrderItem implements Serializable{ private Integer id; private String productname; private Float price; //在多的一方,用Order对象进行描述. private Orders orders; @ManyToOne(cascade=CascadeType.REFRESH,optional=false,targetEntity=Orders.class) @JoinColumn(name="ORDERID") public Orders getOrders() { return orders; } public void setOrders(Orders orders) { this.orders = orders; }22
  • 23. 测试代码2-1//通过单的一方,查找多的一方。 public Set loadOrderItem(Integer id) { // TODO Auto-generated method stub Orders orders = eManager.find(Orders.class, id); if(orders!=null){ return orders.getOrderItem(); } return null; }//通过多的一方,查找单的一方。 public Orders loadbymany(Integer id) { Orders orders= new Orders(); OrderItem orderItem = eManager.find(OrderItem.class,id); orders = orderItem.getOrders(); return orders; }23
  • 24. 测试代码2-1public boolean addOnOne(Integer orderid,String ordername1,String ordername2) { try { Orders orders = new Orders(); orders.setOrderid(orderid); orders.setAmount(new Float(500)); OrderItem oItem1 = new OrderItem(); oItem1.setOrders(orders); oItem1.setProductname(ordername1); oItem1.setPrice(new Float(1000)); oItem1.setId(10); OrderItem oItem2 = new OrderItem(); oItem2.setOrders(orders); oItem2.setProductname(ordername2); oItem2.setPrice(new Float(1000)); oItem2.setId(11); orders.getOrderSet().add(oItem1); orders.getOrderSet().add(oItem2); eManager.persist(orders); } catch (Exception e) { // TODO: handle exception return false; } return true; }在单的一方增加对象的方法。24
  • 25. 测试代码2-2//通过多的一方,查找单的一方 InitialContext ctx=new InitialContext(); OrderManager stm = (OrderManager)ctx.lookup("OrderManagerBean/remote"); Set set = (Set)stm.loadOrderItem(1); Iterator iterator = set.iterator(); while (iterator.hasNext()) { OrderItem orders = (OrderItem)iterator.next(); System.out.println("定单1下面的产品名称有:"+orders.getProductname());//通过单的一方,查找多的一方 InitialContext ctx=new InitialContext(); OrderManager stm = (OrderManager)ctx.lookup("OrderManagerBean/remote"); Orders orders = stm.loadbymany(2); System.out.println("定单明细编号为2的定单编号是"+orders.getOrderid());25
  • 26. 多对一关系中--注意11、有关mapping by属性设置26
  • 27. 多对一关系中--注意22、关联关系应用中,我们尽量将属性的注释写在方法之上。3、在进行Set映射对象描述的时候,一定需要实例化对象 //在单的一方,用一个Set进行概括. private Set orderSet = new HashSet();27
  • 28. 关系与对象映射双向一对一映射 在二个对象中,互相包含一个对方的属性,形成了一种双向一对一的关系。28
  • 29. 关系与对象映射双向一对一映射 在双向的一对一关联中,我们需要在关系被维护端(inverse side)中的@OneToOne注释中指定mappedBy,以指定它是这一关联中的被维护端。同时需要在关系维护端(owner side)建立外键列指向关系被维护端的主键列。 指定OneToOne关系指定外键字段,指定此实体为owner side29
  • 30. 关系与对象映射双向一对一关系Public @interface OneToOne{ CascadeType[] cascade() default {} FetchType fetch() default FetchType,EAGER Class targetEntity() default void.class String mappedBy(); boolean optional() default true }与ManyToOne中属性一样Optional的值设为True用于指定允许外键字段为null Optional的值设为false用于指定不允许外键字段为null30
  • 31. 实现思路3-11、Person实体private Integer personid; private String personname; private Idcard idcard;//取对象的实例 @OneToOne(cascade=CascadeType.ALL,optional=true) //name 取数据库中的字段,映射对象为IdCard的主键映射对象,唯一特性 @JoinColumn(name="idcardid",referencedColumnName="id",unique=true) public Idcard getIdcard() { return idcard;} public void setIdcard(Idcard idcard) { this.idcard = idcard;} @Id public Integer getPersonid() { return personid;} public void setPersonid(Integer personid) { this.personid = personid; } @Column public String getPersonname() { return personname;} public void setPersonname(String personname) { this.personname = personname;}31
  • 32. 实现思路3-22、Idcard实体@Entity @Table(name="Idcard",schema="t27") public class Idcard implements Serializable { private Integer id; private String cardno; private Person person; //mappedBy指向对象的实例,则由对象管理。 @OneToOne(cascade=CascadeType.REFRESH,optional=false,mappedBy="idcard") public Person getPerson() { return person; } public void setPerson(Person person) { this.person = person;} @Column public String getCardno() { return cardno;} public void setCardno(String cardno) { this.cardno = cardno;} @Id public Integer getId() { return id;} public void setId(Integer id) { this.id = id;}32
  • 33. 实现思路3-33、写一个远程无状态sessionBean进行操作@PersistenceContext(unitName="StuInfo") EntityManager eManager; public boolean saveOne(String perName,String cardNo) { try { Person person = new Person(); person.setPersonname(perName); person.setPersonid(5); Idcard idcard = new Idcard(); idcard.setId(5); idcard.setCardno(cardNo); person.setIdcard(idcard); idcard.setPerson(person); //此处由person管理,不能由card管理 eManager.persist(person); //eManager.persist(idcard); } catch (Exception e) { // TODO: handle exception return false; } return true; }33
  • 34. 关系与对象映射双向多对多关系 在现实生活中存在很多多对多关系,比如说学生与老师的关系,多对多关系我们通过采用中间表的策略来建立映射策略。34
  • 35. 关系与对象映射双向多对多关系 在双向多对多关系中,我们必顺指定一个关系维护端(owner side),同样我们可以通过@ManyToMany注释中指定mappedBy属性来标识其为关系维护端。指定对应的多对多关系35
  • 36. 关系与对象映射双向多对多关系 Public @interface ManyToMany{ CascadeType[] cascade() default {} FetchType fetch() default FetchType.LAZY Class targetEntity() default void.class String mappedBy(); }与OneToMany中的属性一样@JoinTable用于指定中间表信息36
  • 37. 关系与对象映射双向多对多关系 在双向多对多关系中,只需要在关系维护端定义此属性。 Public @interface JoinTable{ String catalog() default “”; String schema() default “”; String name() default “”; JoinColumn[] joinColumns() default {}; JoinColumn[] inverseJoinColumns() default {}; UniqueConstraints UniquenConstraints() default {}; }中间表在数据库的相关信息指定与关系维护端关联的字段指定与关系被维护端关联的字段37
  • 38. 实现思路1、Student 实体//属性 private Integer stuid; private String stuname; private Set teacher = new HashSet();//stu_tea 关联表, joinColumns 关联表字段关系,inverseJoinColumns 反转关系 @ManyToMany(cascade ={ CascadeType.PERSIST, CascadeType.MERGE }, fetch = FetchType.EAGER,targetEntity=Teacher.class) @JoinTable(name = "stu_tea", joinColumns = { @JoinColumn(name = "stu_id",referencedColumnName = "stuid") }, inverseJoinColumns = { @JoinColumn(name = "tea_id", referencedColumnName = "teaid") }) public Set getTeacher() { return teacher; } public void setTeacher(Set teacher) { this.teacher = teacher; }38
  • 39. 实现思路2、teacher 实体private Integer teaid; private String teaname; private Set student = new HashSet();@ManyToMany(mappedBy="teacher",targetEntity = Student.class) public Set getStudent() { return student; } public void setStudent(Set student) { this.student = student; }39
  • 40. 实现思路3、测试方法public boolean save(String teaname1, String teaname2 , String stuname1, String stuname2){ Student stu1 = new Student();//学员1 stu1.setStuname(stuname1); stu1.setStuid(1); …//学员2 Teacher tea1 = new Teacher();//教师1 tea1.setTeaname(teaname1); tea1.setTeaid(1); …//教师2 tea1.getStudent().add(stu1);//添加交叉关系 tea1.getStudent().add(stu2); tea2.getStudent().add(stu1); tea2.getStudent().add(stu2); stu1.getTeacher().add(tea1); stu1.getTeacher().add(tea2); stu2.getTeacher().add(tea1); stu2.getTeacher().add(tea2); em.persist(stu1);//保存学员,它是主控方 em.persist(stu2); }40
  • 41. 总结1、JPA的操作关系。 2、关联关系。41
  • 42. Thank you42