一、JPA概述以及它和Hibernate之间的关系
1.1.Hibernate 概述
JPA Java Persistence API,是EJB3规范中负责对象持久化的应用程序编程接口(ORM接口),它定义一系列的注释。这些注释大体可分为:类级别注释、方法级别注释、字段级别注释。给实体类添加适当的注释可以在程序运行时告诉Hibernate如何将一个实体类保存到数据库中以及如何将数据以对象的形式从数据库中读取出来。
目前有两种注释方案可以确定对象与表格之间的对应关系:一种是注释实体类的属性字段(字段级别注释),成为字段访问方式(field access mode);另一种是注释实体类的属性访问方法(方法级别注释),称为属性访问方式(property access mode)。
1.2 JPA与Hibernate 的区别
JPA和Hibernate之间的关系,可以简单的理解为JPA是标准接口,Hibernate是实现。
那么Hibernate是如何实现与JPA的这种关系的呢。Hibernate主要是通过三个组件来实现的,及hibernate-annotation、hibernate-entitymanager和hibernate-core。
- hibernate-annotation:是Hibernate支持annotation方式配置的基础,它包括了标准的JPA annotation以及Hibernate自身特殊功能的annotation。
- hibernate-core:是Hibernate的核心实现,提供了Hibernate所有的核心功能。
- hibernate-entitymanager:实现了标准的JPA,可以把它看成hibernate-core和JPA之间的适配器,它并不直接提供ORM的功能,而是对hibernate-core进行封装,使得Hibernate符合JPA的规范。
二、JPA的环境搭建
2.1 主要配置文件
使用JPA可以省去配置每个实体类的.xml 文件,只需直接在实体类中用注解的方式直接说明即可。先在src目录下的META-INF文件下创建persistence.xml配置文件,如下图所示:
persistence.xml配置内容如下图所示:
<?xml version="1.0" encoding="UTF-8"?> <persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0"> <!-- 配置持久化单元 可以制定多个持久化单元,但名称不可重复,name用于指定持久化单元名称 transaction-type:指定事务的类型 RESCOURCE_LOCAL:指的是本地代码事务 --> <persistence-unit name="myJPAUnit" transaction-type="RESOURCE_LOCAL"> <!-- JPA规范的提供商 可以不写 --> <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider> <!-- 指定由JPA注解的实体类位置 可以不写--> <class>com.Kevin.domain.Customer</class> <!-- 连接数据库相关的一些配置,都是Hibernate的,所以只需要把之前Hibernate配置文件中的内容拷贝过来即可 --> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/> <property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/> <property name="hibernate.connection.url" value="jdbc:mysql:///hibernateday4"/> <property name="hibernate.connection.username" value="root"/> <property name="hibernate.connection.password" value="admin"/> <!-- Hibernate显示SQL语句 --> <property name="hibernate.show_sql" value="true"/> <!-- Hibernate格式化SQL语句 --> <property name="hibernate.format_sql" value="true"/> <property name="hibernate.hbm2ddl.auto" value="update"/> <property name="hibernate.connection.provider_class" value="org.hibernate.connection.C3P0ConnectionProvider"/> </properties> </persistence-unit> </persistence>
获取JPA操作数据库的对象
在src目录下com.Kevin.utils包中创建JPAUtil类用来获取数据库操作对象,文件目录如下图:
获取方式如下:
package com.Kevin.utils; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.Persistence; /** * JPA的工具类 * @author Kevin * */ public class JPAUtil { //相当于SessionFactory private static EntityManagerFactory factory; private static ThreadLocal<EntityManager> tl; static{ factory=Persistence.createEntityManagerFactory("myJPAUnit"); //名称要与persistence.xml文件中的持久化单元name一致 tl=new ThreadLocal<EntityManager>(); } /** * 获取JPA操作数据库的对象 */ // public static EntityManager createEntityManager(){ // return factory.createEntityManager(); // } /** * 获取EntityManager对象 * @return */ public static EntityManager createEntityManager(){ //从当前线程上获取EntityManager对象 EntityManager em=tl.get(); if(em==null){ em=factory.createEntityManager(); tl.set(em); } return tl.get(); } public static void main(String[]args){ createEntityManager(); } }
2.2 基本映射注释简介
2.2.1 @Entity:映射实体类(必须)
@Entity(name="EntityName") ,name 为可选 , 对应数据库中一的个表。
2.2.2 @Table:映射数据库表名(可选)
@Table(name="",catalog="",schema="") , 通常和 @Entity 配合使用 , 只能标注在实体的 class 定义处 , 表示实体对应的数据库表的信息。
name: 可选 , 表示表的名称 . 默认地 , 表名和实体名称一致 , 只有在不一致的情况下才需要指定表名。
catalog: 可选 , 表示 Catalog 名称 , 默认为 Catalog(""). schema: 可选 , 表示 Schema 名称 , 默认为 Schema(“”)。
2.2.3 @Id:映射生成主键(必选)
@id 定义了映射到数据库表的主键的属性 , 一个实体只能有一个属性被映射为主键 。置于 getXxxx() 前 。
@GeneratedValue(strategy=GenerationType,generator="") 可选 strategy: 表示主键生成策略 , 有 AUTO,INDENTITY,SEQUENCE 和 TABLE 4 种 , 分别表示让 ORM 框架自动选择。根据数据库的 Identity 字段生成 , 根据数据库表的 Sequence 字段生成 , 以有根据一个额外的表生成主键 , 默认为 AUTO 。
generator: 表示主键生成器的名称 , 这个属性通常和 ORM 框架相关 , 例如 ,Hibernate 可以指定 uuid 等主键生成方式。
2.2.4 @Column:映射表格列(可选)
@Column 描述了数据库表中该字段的详细定义 , 这对于根据 JPA 注解生成数据库表结构的工具非常有作用 。
name: 表示数据库表中该字段的名称 , 默认情形属性名称一致 。nullable: 表示该字段是否允许为 null, 默认为 true。
unique: 表示该字段是否是唯一标识 , 默认为 false length: 表示该字段的大小 , 仅对 String 类型的字段有效。
insertable: 表示在 ORM 框架执行插入操作时 , 该字段是否应出现 INSETRT 语句中 , 默认为 true。
updateable: 表示在 ORM 框架执行更新操作时 , 该字段是否应该出现在 UPDATE 语句中 , 默认为 true. 对于一经创建就不可以更改的字段 , 该属性非常有用 , 如对于 birthday 字段 。
columnDefinition: 表示该字段在数据库中的实际类型 . 通常 ORM 框架可以根据属性类型自动判断数据库中字段的类型 , 但是对于 Date 类型仍无法确定数据库中字段类型究竟是 DATE,TIME 还是 TIMESTAMP. 此外 ,String 的默认映射类型为 VARCHAR, 如果要将 String 类型映射到特定数据库的 BLOB 或 TEXT 字段类型 , 该属性非常有用 .。
2.2.5 @Transient:定义暂态属性(可选)
@Transient 表示该属性并非一个到数据库表的字段的映射 ,ORM 框架将忽略该属性。如果一个属性并非数据库表的字段映射 , 就务必将其标示为 @Transient, 否则 ,ORM 框架默认其注解为 @Basic。
2.3 关联类映射注释简介
2.3.1 @ManyToOne(可选)
@ManyToOne(fetch=FetchType,cascade=CascadeType )
@ManyToOne 表示一个多对一的映射 , 该注解标注的属性通常是数据库表的外键 。
optional: 是否允许该字段为 null, 该属性应该根据数据库表的外键约束来确定 , 默认为 true 。
fetch: 表示抓取策略 , 默认为 FetchType.EAGER cascade: 表示默认的级联操作策略 , 可以指定为 ALL,PERSIST,MERGE,REFRESH 和 REMOVE 中的若干组合 , 默认为无级联操作 。
targetEntity: 表示该属性关联的实体类型 . 该属性通常不必指定 ,ORM 框架根据属性类型自动判断 targetEntity。
2.3.2 @JoinColumn(可选)
@JoinColumn 和 @Column 类似 , 介量描述的不是一个简单字段 , 而一一个关联字段 , 例如 . 描述一个 @ManyToOne 的字段 。
name: 该字段的名称 . 由于 @JoinColumn 描述的是一个关联字段 , 如 ManyToOne, 则默认的名称由其关联的实体决定。例如 , 实体 Order 有一个 user 属性来关联实体 User, 则 Order 的 user 属性为一个外键 , 其默认的名称为实体 User 的名称 + 下划线 + 实体 User 的主键名称
2.3.3 @OneToMany
@OneToMany(fetch=FetchType,cascade=CascadeType)
@OneToMany 描述一个一对多的关联 , 该属性应该为集体类型 , 在数据库中并没有实际字段 。
fetch: 表示抓取策略 , 默认为 FetchType.LAZY, 因为关联的多个对象通常不必从数据库预先读取到内存。
cascade: 表示级联操作策略 , 对于 OneToMany 类型的关联非常重要 , 通常该实体更新或删除时 , 其关联的实体也应当被更新或删除 例如 : 实体 User 和 Order 是 OneToMany 的关系 , 则实体 User 被删除时 , 其关联的实体 Order 也应该被全部删除
2.3.4 @OneToOne(可选)
@OneToOne(fetch=FetchType,cascade=CascadeType)
@OneToOne 描述一个一对一的关联 。
fetch: 表示抓取策略 , 默认为 FetchType.LAZY 。
cascade: 表示级联操作策略。
2.3.5 @ ManyToMany(可选)
@ManyToMany 描述一个多对多的关联 . 多对多关联上是两个一对多关联 , 但是在 ManyToMany 描述中 , 中间表是由 ORM 框架自动处理。
targetEntity: 表示多对多关联的另一个实体类的全名 , 例如 :package.Book.class。
mappedBy: 表示多对多关联的另一个实体类的对应集合属性名称。
三、JPA入门案例和CRUD操作(单表)
3.1 创建客户实体类
package com.Kevin.domain; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; /** * 客户的实体类 * @author Kevin * 使用的注解都是JPA规范,所以导包要导入javax.persistence包下 * */ @Entity //表名该类是一个实体类 @Table(name="cst_customer") //建立当前类和数据库的对应关系 public class Customer{ @Id // @Column(name="cust_id") //表明对应数据库的主键字段是cust_id @GeneratedValue(strategy=GenerationType.IDENTITY) //指定主键生成策略,strategy:使用JPA中提供的主键生成策略,此属性是用不了;generator属性可以使用hibernate主键生成策略 private Long cust_id; @Column(name="cust_name") private String cust_name; @Column(name="cust_address") private String cust_address; @Column(name="cust_industry") private String cust_industry; @Column(name="cust_level") private String cust_level; @Column(name="cust_phone") private String cust_phone; @Column(name="cust_mobile") private String cust_mobile; public Long getCust_id() { return cust_id; } public void setCust_id(Long cust_id) { this.cust_id = cust_id; } public String getCust_name() { return cust_name; } public void setCust_name(String cust_name) { this.cust_name = cust_name; } public String getCust_address() { return cust_address; } public void setCust_address(String cust_address) { this.cust_address = cust_address; } public String getCust_industry() { return cust_industry; } public void setCust_industry(String cust_industry) { this.cust_industry = cust_industry; } public String getCust_level() { return cust_level; } public void setCust_level(String cust_level) { this.cust_level = cust_level; } public String getCust_phone() { return cust_phone; } public void setCust_phone(String cust_phone) { this.cust_phone = cust_phone; } public String getCust_mobile() { return cust_mobile; } public void setCust_mobile(String cust_mobile) { this.cust_mobile = cust_mobile; } @Override public String toString() { return "Customer [cust_id=" + cust_id + ", cust_name=" + cust_name + ", cust_address=" + cust_address + ", cust_industry=" + cust_industry + ", cust_level=" + cust_level + ", cust_phone=" + cust_phone + ", cust_mobile=" + cust_mobile + "]"; } }
3.2 编写CRUD测试代码
3.2.1 保存操作
//保存操作 @Test public void test1(){ //创建客户对象 Customer c=new Customer(); c.setCust_name("Kevin"); //1.获取EntityManager EntityManager em=JPAUtil.createEntityManager(); //2.获取事务对象,并开启事务 EntityTransaction tx=em.getTransaction(); tx.begin(); //3.执行保存操作 em.persist(c); //4.提交事务 tx.commit(); //5.关闭资源 em.close(); }
3.2.2 更新操作(两种方式)
第一种:正常更新方式(update)
@Test public void test3(){ //1.获取EntityManager EntityManager em=JPAUtil.createEntityManager(); //2.获取事务对象,并开启事务 EntityTransaction tx=em.getTransaction(); tx.begin(); //3.执行更新操作(将需要更新的对象查询出来) Customer c=em.find(Customer.class, 1l); //更新客户地址 c.setCust_address("China");
//4.提交事务 tx.commit(); //5.关闭资源 em.close(); }
第二种:合并方式保存(merge,将两个实体合并)
@Test public void test4(){ //1.获取EntityManager EntityManager em=JPAUtil.createEntityManager(); //2.获取事务对象,并开启事务 EntityTransaction tx=em.getTransaction(); tx.begin(); //3.执行更新操作(将需要更新的对象查询出来) Customer c=em.find(Customer.class, 1l); //更新客户地址 c.setCust_mobile("66666666");; em.merge(c); //4.提交事务 tx.commit(); //5.关闭资源 em.close(); }
3.2.3 删除操作
//删除 @Test public void test5(){ //获取EntityManager对象 EntityManager em=JPAUtil.createEntityManager(); //获取事务对象,并开启事务 EntityTransaction tx=em.getTransaction(); tx.begin(); //获取操作对象 Customer c=em.find(Customer.class, 1l); //删除对象 em.remove(c); //提交事务 tx.commit(); //关闭资源 em.close(); }
3.2.4 基本查询
//查询所有 /** * 涉及的对象: * JPA的Query: * 如何获取对象:EntityManager的createQuery(String sql) * 参数含义:JPAL:Java Persistence Query Language * 写法与HQL很相似,也是把表名换成类名,把字段名换成属性名称 * 在写查询所有时,不能直接用 from 类 * 需要使用select关键字 */ @Test public void test6(){ //获取EntityManager对象 EntityManager em=JPAUtil.createEntityManager(); //获取事务对象,并开启事务 EntityTransaction tx=em.getTransaction(); tx.begin(); //获取JPA的查询对象Query Query query=em.createQuery("select c from Customer c"); //c是一个别名 //执行方法获取结果集 List list=query.getResultList(); for(Object o:list) System.out.println(o); //提交事务 tx.commit(); //关闭资源 em.close(); }
3.2.5 条件查询
@Test public void test7(){ //获取EntityManager对象 EntityManager em=JPAUtil.createEntityManager(); //获取事务对象,并开启事务 EntityTransaction tx=em.getTransaction(); tx.begin(); //获取JPA的查询对象Query Query query=em.createQuery("select c from Customer c where cust_name like ?"); //给占位符赋值 query.setParameter(1, "%k%"); //执行方法获取结果集 List list=query.getResultList(); for(Object o:list) System.out.println(o); //提交事务 tx.commit(); //关闭资源 em.close(); }
//多条件查询 @Test public void test8(){ //获取EntityManager对象 EntityManager em=JPAUtil.createEntityManager(); //获取事务对象,并开启事务 EntityTransaction tx=em.getTransaction(); tx.begin(); //获取JPA的查询对象Query Query query=em.createQuery("select c from Customer c where cust_name like ? and cust_level = ? "); //给占位符赋值 query.setParameter(1, "%K%"); query.setParameter(2, "5"); //执行方法获取结果集 List list=query.getResultList(); for(Object o:list) System.out.println(o); //提交事务 tx.commit(); //关闭资源 em.close(); }
TIps: 1. persist()方法相当于是save()操作;
2. remove()对应的是delete();
3. find()方法对应get()方法;
4. getReference()是延迟加载;
5. find()是立即加载;
6. uniqueResult()对应getSingleResult(),返回唯一的结果。
7. merge()和update()相似,但是merge干的活update有些不能干;
3.2.6 区别merge和update
当查询了一个对象后,关闭session,再次查询了该对象,并且修改了该对象。此时如果使用update方法时会报错,因为第一次查完后关闭了session,对象的状态转变成了托管态,而此时查询该对象,修改的时候是持久态,对象的状态是不一样的,在一级缓存外边还有一个修改对象。此时更新的话,由于两个对象的OID是一样的,但是却发生了修改,使用update的话,两个对象是不能合并的,只能用merge()方法将其更新,即将两个对象合并。
@Test public void test10(){ /** * 查询ID为1的客户 * 关闭EntityManager(清空了一级缓存和快照) * 修改id为1的客户的地址为America * 在此获取EntityManager * 再次查询ID为1的客户 * 更新刚才修改的客户 */ //获取EntityManager对象 EntityManager em=JPAUtil.createEntityManager(); //获取事务对象,并开启事务 EntityTransaction tx=em.getTransaction(); tx.begin(); Customer c=em.find(Customer.class, 1L);//持久态 tx.commit(); em.close(); //修改客户信息 c.setCust_address("America");//托管态 //获取EntityManager对象 EntityManager em1=JPAUtil.createEntityManager(); //获取事务对象,并开启事务 EntityTransaction tx1=em.getTransaction(); tx1.begin(); //再次查询 Customer c1=em.find(Customer.class, 1L);//持久态 //更新操作 em1.persist(c);//将托管态转换为持久态,update(persist)方法是不行的,必须要用到merge方法才可以的 em1.merge(c); tx1.commit(); em1.close(); }
四、 JPA中实体一对多映射配置及操作
4.1 一对多实体类注解编写
客户实体类:
package com.Kevin.domain; import java.util.HashSet; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.Table; /** * 客户的实体类 * @author Kevin * 使用的注解都是JPA规范,所以导包要导入javax.persistence包下 * */ @Entity //表情该类是一个实体类 @Table(name="cst_customer") //建立当前类和数据库的对应关系 public class Customer{ @Id // @Column(name="cust_id") //表明对应数据库的主键字段是cust_id @GeneratedValue(strategy=GenerationType.IDENTITY) //指定主键生成策略,strategy:使用JPA中提供的主键生成策略,此属性是用不了;generator属性可以使用hibernate主键生成策略 private Long cust_id; @Column(name="cust_name") private String cust_name; @Column(name="cust_address") private String cust_address; @Column(name="cust_industry") private String cust_industry; @Column(name="cust_level") private String cust_level; @Column(name="cust_phone") private String cust_phone; @Column(name="cust_mobile") private String cust_mobile; //一对多关系映射:一个客户可以有多个联系人 //targetEntity=Linkman.class 可以不写 @OneToMany(targetEntity=Linkman.class,mappedBy="customer",cascade=CascadeType.ALL,fetch=FetchType.EAGER) private Set<Linkman> linkmans=new HashSet<Linkman>(0); public Long getCust_id() { return cust_id; } public void setCust_id(Long cust_id) { this.cust_id = cust_id; } public String getCust_name() { return cust_name; } public void setCust_name(String cust_name) { this.cust_name = cust_name; } public String getCust_address() { return cust_address; } public void setCust_address(String cust_address) { this.cust_address = cust_address; } public String getCust_industry() { return cust_industry; } public void setCust_industry(String cust_industry) { this.cust_industry = cust_industry; } public String getCust_level() { return cust_level; } public void setCust_level(String cust_level) { this.cust_level = cust_level; } public String getCust_phone() { return cust_phone; } public void setCust_phone(String cust_phone) { this.cust_phone = cust_phone; } public String getCust_mobile() { return cust_mobile; } public void setCust_mobile(String cust_mobile) { this.cust_mobile = cust_mobile; } public Set<Linkman> getLinkmans() { return linkmans; } public void setLinkmans(Set<Linkman> linkmans) { this.linkmans = linkmans; } @Override public String toString() { return "Customer [cust_id=" + cust_id + ", cust_name=" + cust_name + ", cust_address=" + cust_address + ", cust_industry=" + cust_industry + ", cust_level=" + cust_level + ", cust_phone=" + cust_phone + ", cust_mobile=" + cust_mobile + "]"; } }
联系人实体类:
package com.Kevin.domain; /** * 创建联系人实体类 * `lkm_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '联系人编号(主键)', `lkm_name` varchar(16) DEFAULT NULL COMMENT '联系人姓名', `lkm_cust_id` bigint(32) NOT NULL COMMENT '客户id', `lkm_gender` char(1) DEFAULT NULL COMMENT '联系人性别', `lkm_phone` varchar(16) DEFAULT NULL COMMENT '联系人办公电话', `lkm_mobile` varchar(16) DEFAULT NULL COMMENT '联系人手机', `lkm_email` varchar(64) DEFAULT NULL COMMENT '联系人邮箱', `lkm_qq` varchar(16) DEFAULT NULL COMMENT '联系人qq', `lkm_position` varchar(16) DEFAULT NULL COMMENT '联系人职位', `lkm_memo` varchar(512) DEFAULT NULL COMMENT '联系人备注', */ import java.io.Serializable; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.Table; @Entity @Table(name="cst_linkman") public class Linkman implements Serializable { @Id @Column(name="lkm_id") @GeneratedValue(strategy=GenerationType.IDENTITY) private Long lkm_id; @Column(name="lkm_name") private String lkm_name; @Column(name="lkm_gender") private String lkm_gender; @Column(name="lkm_mobile") private String lkm_mobile; @Column(name="lkm_phone") private String lkm_phone; @Column(name="lkm_email") private String lkm_email; @Column(name="lkm_qq") private String lkm_qq; @Column(name="lkm_position") private String lkm_position; @Column(name="lkm_memo") private String lkm_memo; //一对多关系影射 //从表实体包含主表实体的对象引用 @ManyToOne(targetEntity=Customer.class,cascade=CascadeType.ALL) @JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id") private Customer customer; public Long getLkm_id() { return lkm_id; } public void setLkm_id(Long lkm_id) { this.lkm_id = lkm_id; } public String getLkm_name() { return lkm_name; } public void setLkm_name(String lkm_name) { this.lkm_name = lkm_name; } public String getLkm_gender() { return lkm_gender; } public void setLkm_gender(String lkm_gender) { this.lkm_gender = lkm_gender; } public String getLkm_mobile() { return lkm_mobile; } public void setLkm_mobile(String lkm_mobile) { this.lkm_mobile = lkm_mobile; } public String getLkm_phone() { return lkm_phone; } public void setLkm_phone(String lkm_phone) { this.lkm_phone = lkm_phone; } public String getLkm_email() { return lkm_email; } public void setLkm_email(String lkm_email) { this.lkm_email = lkm_email; } public String getLkm_qq() { return lkm_qq; } public void setLkm_qq(String lkm_qq) { this.lkm_qq = lkm_qq; } public String getLkm_position() { return lkm_position; } public void setLkm_position(String lkm_position) { this.lkm_position = lkm_position; } public String getLkm_memo() { return lkm_memo; } public void setLkm_memo(String lkm_memo) { this.lkm_memo = lkm_memo; } public Customer getCustomer() { return customer; } public void setCustomer(Customer customer) { this.customer = customer; } @Override public String toString() { return "Linkman [lkm_id=" + lkm_id + ", lkm_name=" + lkm_name + ", lkm_gender=" + lkm_gender + ", lkm_mobile=" + lkm_mobile + ", lkm_phone=" + lkm_phone + ", lkm_email=" + lkm_email + ", lkm_qq=" + lkm_qq + ", lkm_position=" + lkm_position + ", lkm_memo=" + lkm_memo + "]"; } }
4.2 一对多关联关系的相关操作
4.2.1 保存操作
/** * 保存操作 * 创建一个客户和一个联系人 * 建立客户和联系人的双向关联关系 * 先保存客户,在保存联系人 */ @Test public void test1(){ Customer c=new Customer(); c.setCust_name("Kevin_one2many"); Linkman lkm=new Linkman(); lkm.setLkm_name("Kevin_onw2many"); c.getLinkmans().add(lkm); lkm.setCustomer(c); EntityManager em=JPAUtil.creatEntityManager(); EntityTransaction tx=em.getTransaction(); tx.begin(); em.persist(c); em.persist(lkm); tx.commit(); em.close(); }
4.2.2 更新操作
此时需要配置级联操作:要级联操作哪一方就应该在那一方的上边进行注解配置cascade=CsacadeType.PERSIST属性,即保存或者更新客户的同时保存联系人,但时cascade=CsacadeType.PERSIST只是级联更新。
其中mappedBy是映射来自,相当于inverse,即主表不在关心从表的信息,让联系人去维护
/** * 更新操作 * 创建一个联系人 * 查询id为*客户 * 为该客户分配该联系人 * 更新客户 */ @Test public void test2(){ Linkman lkm=new Linkman(); lkm.setLkm_name("Kevin_onw2many3"); EntityManager em=JPAUtil.creatEntityManager(); EntityTransaction tx=em.getTransaction(); tx.begin(); Customer c=em.find(Customer.class, 7l); c.getLinkmans().add(lkm); lkm.setCustomer(c); //em.merge(c); tx.commit(); em.close(); }
4.2.3 删除操作
删除主表:若在客户上边配置了放弃维护,即mappedBy="customer",直接删除指标会报错,若此时还想要删除的话,需要配置cascade=CascadeType.DELET或者cascade=CascadeType.ALL就可以删除。
需要注意的是:联系人(从表)也可以配置cascade=CascadeType.ALL来进行操作。
/** * 删除操作 */ @Test public void test3(){ EntityManager em=JPAUtil.creatEntityManager(); EntityTransaction tx=em.getTransaction(); tx.begin(); Customer c=em.find(Customer.class, 6l); em.remove(c); tx.commit(); em.close(); }
4.2.4 查询操作
JPA中也可以使用对象导航查询,也可以设置查询的时机。
延迟加载的特点:真正用到该对象的时候才开始查询改对象的属性。
如果是立即加载,需要在Customer的set集合的注解中加入下边的语句:fetch=FetchType.EAGER,如下图:
其原理是利用了左外连接查询的方式实现了立即加载。没写是EAGER,即默认是EAGER。LinkMan中也可是设置成立即加载。
mappedBy是映射来自,相当于inverse,即主表不在关心从表的信息,让联系人去维护。
//根据客户查询联系人 @Test public void test1(){ EntityManager em=JPAUtil.creatEntityManager(); EntityTransaction tx=em.getTransaction(); tx.begin(); //查询id为7的客户 Customer c=em.find(Customer.class, 7l); System.out.println(c); //查询当前客户下的联系人 Set<Linkman> linkmans=c.getLinkmans(); System.out.println(linkmans); tx.commit(); em.close(); }
//根据联系人查询客户 @Test public void test2(){ EntityManager em=JPAUtil.creatEntityManager(); EntityTransaction tx=em.getTransaction(); tx.begin(); //查询id为7的客户 Linkman lkm=em.find(Linkman.class, 4l); System.out.println(lkm); //查询当前客户下的联系人 Customer c=lkm.getCustomer(); System.out.println(c); tx.commit(); em.close(); }
五、 JPA中实体多对多映射配置及操作
5.1 多对多的实体类注解编写
在角色实体对象中,如果配置了中间表的表名和在中间表中的列明,则在另外多的一方中只需要配置@ManyToMany(mappedBy="users"),如下图:
哪一方写mappedBy哪一方就不在关心创建中间表了,即让另外一方不在关心创建中间表。
用户实体类:
package com.Kevin.domain; /** * 用户实体类 */ import java.io.Serializable; import java.util.HashSet; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.ManyToMany; import javax.persistence.Table; import org.hibernate.annotations.GenericGenerator; @Entity @Table(name="sys_user") public class SysUser implements Serializable { @Id @Column(name="user_id") @GenericGenerator(name="uuid",strategy="uuid") @GeneratedValue(generator="uuid") private String userId; @Column(name="user_name") private String userName; @Column(name="user_password") private String userPassword; @Column(name="user_state") private Integer userState; //多对多关系映射: @ManyToMany(mappedBy="users",cascade=CascadeType.ALL) private Set<SysRole> roles=new HashSet<SysRole>(0); public Set<SysRole> getRoles() { return roles; } public void setRoles(Set<SysRole> roles) { this.roles = roles; } public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserPassword() { return userPassword; } public void setUserPassword(String userPassword) { this.userPassword = userPassword; } public Integer getUserState() { return userState; } public void setUserState(Integer userState) { this.userState = userState; } @Override public String toString() { return "SysUser [userId=" + userId + ", userName=" + userName + ", userPassword=" + userPassword + ", userState=" + userState + "]"; } }
角色实体类:
package com.Kevin.domain; /** * 角色的实体类 */ import java.io.Serializable; import java.util.HashSet; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.Table; import org.hibernate.annotations.GenericGenerator; @Entity @Table(name="sys_role") public class SysRole implements Serializable { @Id @Column(name="role_id") @GenericGenerator(name="uuid",strategy="uuid") //声明一个主键生成器,name:给生成器起名;strategy:指定的是hibernate中包含的生成策略 @GeneratedValue(generator="uuid") private String roleId; @Column(name="role_name") private String roleName; @Column(name="role_memo") private String roleMemo; //多对多关系映射:一个角色可以赋予多个用户 @ManyToMany(cascade=CascadeType.ALL) //加入一张表 @JoinTable(name="user_role_ref",joinColumns={@JoinColumn(name="role_id",referencedColumnName="role_id")}, //写的是当前实体在中间表的外键字段 inverseJoinColumns={@JoinColumn(name="user_id",referencedColumnName="user_id")} //写的是对方实体在中间表的外键字段 ) private Set<SysUser> users=new HashSet<SysUser>(0); public Set<SysUser> getUsers() { return users; } public void setUsers(Set<SysUser> users) { this.users = users; } public String getRoleId() { return roleId; } public void setRoleId(String roleId) { this.roleId = roleId; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } public String getRoleMemo() { return roleMemo; } public void setRoleMemo(String roleMemo) { this.roleMemo = roleMemo; } @Override public String toString() { return "SysRole [roleId=" + roleId + ", roleName=" + roleName + ", roleMemo=" + roleMemo + "]"; } }
5.2 多对多关联关系的相关操作
5.2.1 一般保存
/** * 保存操作 * 创建2个用户,3个角色 * 让1号用户具备1、2号角色 * 让2号用户具备2、3号角色 * 保存用户和角色 * 正常保存 */ @Test public void test1(){ SysUser u1=new SysUser(); SysUser u2=new SysUser(); u1.setUserName("JPA u1"); u2.setUserName("JPA u2"); SysRole r1=new SysRole(); SysRole r2=new SysRole(); SysRole r3=new SysRole(); r1.setRoleName("JPA r1"); r2.setRoleName("JPA r2"); r3.setRoleName("JPA r3"); //建立用户和角色的关联关系 u1.getRoles().add(r1); u1.getRoles().add(r2); u2.getRoles().add(r2); u2.getRoles().add(r3); r1.getUsers().add(u1); r2.getUsers().add(u2); r2.getUsers().add(u1); r3.getUsers().add(u2); EntityManager em=JPAUtil.creatEntityManager(); EntityTransaction tx=em.getTransaction(); tx.begin(); em.persist(u1); em.persist(u2); em.persist(r1); em.persist(r2); em.persist(r3); tx.commit(); em.close(); }
5.2.2 级联保存
级联操作是指当主控方执行保存、更新或者删除操作时,其关联对象(被控方)也执行相同的操作。在映射文件中通过对 cascade属性的设置来控制是否对关联对象采用级联操作,级联操作对各种关联关系都是有效的。
在JPA的多对多关联关系中中只需设置一方的级联保存属性即可,本文中以用户为例,实现如下:
编写测试代码:
//级联保存 @Test public void test2(){ SysUser u1=new SysUser(); SysUser u2=new SysUser(); u1.setUserName("JPA u1"); u2.setUserName("JPA u2"); SysRole r1=new SysRole(); SysRole r2=new SysRole(); SysRole r3=new SysRole(); r1.setRoleName("JPA r1"); r2.setRoleName("JPA r2"); r3.setRoleName("JPA r3"); //建立用户和角色的关联关系 u1.getRoles().add(r1); u1.getRoles().add(r2); u2.getRoles().add(r2); u2.getRoles().add(r3); r1.getUsers().add(u1); r2.getUsers().add(u2); r2.getUsers().add(u1); r3.getUsers().add(u2); EntityManager em=JPAUtil.creatEntityManager(); EntityTransaction tx=em.getTransaction(); tx.begin(); em.persist(u1); tx.commit(); em.close(); }
5.2.3 删除操作(禁用级联删除)
/** * 删除操作 * 双向级联删除:不管是在JPA还是hibernate,多对多中都禁止使用 */ @Test public void test3(){ EntityManager em=JPAUtil.creatEntityManager(); EntityTransaction tx=em.getTransaction(); tx.begin(); SysUser user=em.find(SysUser.class, "402881ea632f00cf01632f00e6550000"); em.remove(user); tx.commit(); em.close(); }