1、JPA概述
2、JPA中的第一个程序
3、JPA中常用的注解(重点)
4、JPA中的CRUD
5、JPA中的多表映射的配置
6、JPA中多表的增删改
7、对象导航查询
- JPA相关概念
- JPA概述
什么时候JPA?JPA它是一套Java持久化规范。那么JPA和我们前面三天所学的Hibernate有什么关系呢?
- JPA是一套Java持久化规范;而Hibernate实现了JPA规范。
- 在Hibernate中有两套操作数据库的方式,一套是Hibernate自己特有的的方式,前面三天已经学过了;还有一套是实现了JPA规范的方式。
今天我们主要学习Hibernate中实现了JPA规范的操作数据库的方式。
-
- JPA优势
-
- 学习JPA要明确的
a、JPA是一套ORM规范,hibernate实现了JPA规范
b、hibernate中有自己的独立ORM操作数据库方式,也有JPA规范实现的操作数据库方式。
c、在数据库增删改查操作中,我们hibernate和JPA的操作都要会。
- JPA入门
- 第一步:创建工程,引入jar包
要搭建一个JPA的开发环境,除了需要导入前面三天所用的Hibernate的jar包以外,还需要导入hibernate-entitymanager.jar包,该jar包可以从如下路径找到:
完整的jar包如下图所示:
注意:今天我们主要学习JPA规范,但是仅仅只有规范是不行的,还得有规范的实现才行,所以才需要导入Hibernate相关的jar包,因为Hibernate就是JPA规范的实现。
-
- 第二步:创建实体类
创建Customer实体类:
在实体类上加上JPA注解,取代传统的hbm.xml文件的功能。需要使用到的注解有:
- @Entity:如果一个类加上此注解,表示该类是一个实体类。
- @Table:指定该类所映射的表,name属性表示该类所映射的表名。如果类名与表名一致,此注解可以省略。
- @Id:表示与主键列对应的属性。
- @GeneratedValue:表示主键如何生成。strategy属性表示主键的生成策略。GenerationType.IDENTITY表示自动增长。
/**
* 客户实体类
* kevin
*/
@Entity
@Table(name="cst_customer")
public class Customer implements Serializable{
private static final long serialVersionUID = 1L;
@Id
@Column(name="cust_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long custId;
@Column(name="cust_name")
private String custName;
@Column(name="cust_source")
private String custSource;
@Column(name="cust_industry")
private String custIndustry;
@Column(name="cust_level")
private String custLevel;
@Column(name="cust_address")
private String custAddress;
@Column(name="cust_phone")
private String custPhone;
public Long getCustId() {
return custId;
}
public void setCustId(Long custId) {
this.custId = custId;
}
public String getCustName() {
return custName;
}
public void setCustName(String custName) {
this.custName = custName;
}
public String getCustSource() {
return custSource;
}
public void setCustSource(String custSource) {
this.custSource = custSource;
}
public String getCustIndustry() {
return custIndustry;
}
public void setCustIndustry(String custIndustry) {
this.custIndustry = custIndustry;
}
public String getCustLevel() {
return custLevel;
}
public void setCustLevel(String custLevel) {
this.custLevel = custLevel;
}
public String getCustAddress() {
return custAddress;
}
public void setCustAddress(String custAddress) {
this.custAddress = custAddress;
}
public String getCustPhone() {
return custPhone;
}
public void setCustPhone(String custPhone) {
this.custPhone = custPhone;
}
@Override
public String toString() {
return "Customer [custId=" + custId + ", custName=" + custName + ", custSource=" + custSource
+ ", custIndustry=" + custIndustry + ", custLevel=" + custLevel + ", custAddress=" + custAddress
+ ", custPhone=" + custPhone + "]";
}
}
-
- 第三步:创建配置文件
1、在src下新建META-INF文件夹,新建persistence.xml文件。
- 引入schema约束,可以从hibernate-entitymanager-5.0.7.Final.jar/org/hibernate/jpa/persistence_2_0.xsd中拷贝
- 配置提示
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">
<persistence-unit name="myPersistUnit">
<!-- 指定JPA规范的实现:Hibernate的实现 (可省略)-->
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<!-- 指定要被扫描到的实体类 (可省略)-->
<class>cn.itcast.domain.Customer</class>
<properties>
<property name="hibernate.connection.driver_class" value="com.mysql.jdbc.Driver"/>
<property name="hibernate.connection.url" value="jdbc:mysql://localhost:3306/hibernate_itheima20"/>
<property name="hibernate.connection.username" value="root"/>
<property name="hibernate.connection.password" value="123456"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.MySQLDialect"/>
<property name="hibernate.connection.provider_class" value="org.hibernate.connection.C3P0ConnectionProvider"></property>
<property name="hibernate.c3p0.max_size" value="20"></property>
<property name="hibernate.c3p0.min_size" value="5"></property>
<property name="hibernate.c3p0.timeout" value="5000"></property>
<property name="hibernate.c3p0.idle_test_period" value="3000"></property>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
</properties>
</persistence-unit>
</persistence>
-
- 第四步:编写工具类,用于获取JPA操作数据库的对象
创建JPAUtils工具类,封装获取EntityManager的方法,等价于封装HibernateUtils工具类用于获取Session:
JPAUtils的内容如下:
/**
* 用于获取JPA操作数据库对象的工具类
* @author kevin
*/
public class JPAUtils {
//JPA的实体管理器工厂:相当于Hibernte的SessionFactory
private static final EntityManagerFactory em;
//使用静态代码块赋值
static{
//创建JPA的实体管理器工厂:该方法参数必须和persistence.xml中persistence-unit标签的name属性一致
em = Persistence.createEntityManagerFactory("myPersistUnit");
}
/**
* 获取实例管理器的工具方法
* @return
*/
public static EntityManager getEntityManager(){
return em.createEntityManager();
}
}
用到的API的解释:
EntityManagerFactory:它是JPA规范中生产EntityManager的工厂,要保证一个工程中只有一个EntityManagerFactory。作用等价于SessionFactory;
EntityManager:实体管理器,用来做CRUD的,作用等价于Session;
Persistence:JPA规范中的工具类,用于获取EntityManagerFactory的;
-
- 第五步:编写测试类
创建单元测试类TestJPA:
TestJPA内容如下:
/**
* 测试JPA注解里的保存
*/
@Test
public void test1(){
Customer customer = new Customer();
customer.setCustName("蔚来汽车");
customer.setCustIndustry("人工智能");
customer.setCustSource("网络营销");
customer.setCustLevel("VIP客户");
customer.setCustAddress("北京");
customer.setCustPhone("0108650123");
//获取JPA中实体管理器
EntityManager em = JPAUtils.getEntityManager();
//获取事务
EntityTransaction tx = em.getTransaction();
//开启事务
tx.begin();
//保存对象
em.persist(customer);
//提交事务
tx.commit();
//释放资源
em.close();
}
用到的API的解释:
EntityTransaction:JPA规范中的事务接口,用来管理JPA规范中的事务,作用等价于Transaction;
EntityManager.persist():用来保存对象的;作用等价于session.save();
- 常用注解及主键生成策略
- 常用注解说明
@Entity
作用:指定当前类是实体类。写上此注解用于在创建SessionFactory/EntityManager时,加载映射配置。
@Table
作用:指定实体类和表之间的对应关系。
属性:
name:指定数据库表的名称
@Id
作用:指定当前字段是主键。
@GeneratedValue
作用:指定主键的生成方式。JPA的主键生成方式详解见2.4小节的说明。
属性:
strategy :指定主键生成策略。JPA支持四种生成策略,具体介绍看3.2小节。
@Column
作用:指定实体类属性和数据库表里列之间的对应关系
属性:
name:指定数据库表的列名称。
unique:是否唯一
nullable:是否可以为空 inserttable:是否可以插入 updateable:是否可以更新 columnDefinition: 定义建表时创建此列的DDL secondaryTable: 从表名。如果此列不建在主表上(默认建在主表),该属性定义该列所在从表的名字。-
- 主键生成策略
基于annotation的hibernate主键标识为@Id,
其生成规则由@GeneratedValue设定的.这里的@id和@GeneratedValue都是JPA的标准用法。JPA提供的四种标准用法为TABLE,SEQUENCE,IDENTITY,AUTO。具体说明如下:
-
-
- IDENTITY:主键由数据库自动生成(自动增长型)
-
用法:
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long custId;
-
-
- SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列。
-
用法:
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,generator="payablemoney_seq")
@SequenceGenerator(name="payablemoney_seq", sequenceName="seq_payment")
说明:
@SequenceGenerator源码中的定义
@Target({TYPE, METHOD, FIELD})
@Retention(RUNTIME)
public @interface SequenceGenerator {
String name();
String sequenceName() default "";
int initialValue() default 0;
int allocationSize() default 50;
}
name:表示该表主键生成策略的名称,它被引用在@GeneratedValue中设置的“generator”值中。
sequenceName:属性表示生成策略用到的数据库序列名称。 initialValue:表示主键初识值,默认为0。 allocationSize:表示每次主键值增加的大小,例如设置1,则表示每次插入新记录后自动加1,默认为50。-
-
- AUTO:根据底层数据库自动选择一种策略
-
如果底层数据库是mysql,JPA是采用一个特定的表来保存主键。
用法:
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
把Customer的主键生成策略改为AUTO,内容如下:
@Entity
@Table(name="cst_customer")
public class Customer implements Serializable{
private static final long serialVersionUID = 1L;
@Id
@Column(name="cust_id")
@GeneratedValue(strategy=GenerationType.AUTO)
private Long custId;
@Column(name="cust_name")
private String custName;
@Column(name="cust_source")
private String custSource;
@Column(name="cust_industry")
private String custIndustry;
@Column(name="cust_level")
private String custLevel;
@Column(name="cust_address")
private String custAddress;
@Column(name="cust_phone")
private String custPhone;
public Long getCustId() {
return custId;
}
public void setCustId(Long custId) {
this.custId = custId;
}
public String getCustName() {
return custName;
}
public void setCustName(String custName) {
this.custName = custName;
}
public String getCustSource() {
return custSource;
}
public void setCustSource(String custSource) {
this.custSource = custSource;
}
public String getCustIndustry() {
return custIndustry;
}
public void setCustIndustry(String custIndustry) {
this.custIndustry = custIndustry;
}
public String getCustLevel() {
return custLevel;
}
public void setCustLevel(String custLevel) {
this.custLevel = custLevel;
}
public String getCustAddress() {
return custAddress;
}
public void setCustAddress(String custAddress) {
this.custAddress = custAddress;
}
public String getCustPhone() {
return custPhone;
}
public void setCustPhone(String custPhone) {
this.custPhone = custPhone;
}
@Override
public String toString() {
return "Customer [custId=" + custId + ", custName=" + custName + ", custSource=" + custSource
+ ", custIndustry=" + custIndustry + ", custLevel=" + custLevel + ", custAddress=" + custAddress
+ ", custPhone=" + custPhone + "]";
}
}
此时,向客户表中保存一个对象:Hibernate会自动创建一张hibernate_sequence表,用于存储下一个主键,每次需要获取主键时,就查询hibernate_sequence,把查询到的结果作为主键插入到表中,再把hibernate_sequence表中的主键值+1,测试结果如下:
-
-
- TABLE:使用一个特定的数据库表格来保存主键(了解)
-
用法:
@Id
@GeneratedValue(strategy = GenerationType.TABLE)
把Customer的主键生成策略改为TABLE,内容如下:
@Entity
@Table(name="cst_customer")
public class Customer implements Serializable{
private static final long serialVersionUID = 1L;
@Id
@Column(name="cust_id")
@GeneratedValue(strategy=GenerationType.TABLE)
private Long custId;
@Column(name="cust_name")
private String custName;
@Column(name="cust_source")
private String custSource;
@Column(name="cust_industry")
private String custIndustry;
@Column(name="cust_level")
private String custLevel;
@Column(name="cust_address")
private String custAddress;
@Column(name="cust_phone")
private String custPhone;
public Long getCustId() {
return custId;
}
public void setCustId(Long custId) {
this.custId = custId;
}
public String getCustName() {
return custName;
}
public void setCustName(String custName) {
this.custName = custName;
}
public String getCustSource() {
return custSource;
}
public void setCustSource(String custSource) {
this.custSource = custSource;
}
public String getCustIndustry() {
return custIndustry;
}
public void setCustIndustry(String custIndustry) {
this.custIndustry = custIndustry;
}
public String getCustLevel() {
return custLevel;
}
public void setCustLevel(String custLevel) {
this.custLevel = custLevel;
}
public String getCustAddress() {
return custAddress;
}
public void setCustAddress(String custAddress) {
this.custAddress = custAddress;
}
public String getCustPhone() {
return custPhone;
}
public void setCustPhone(String custPhone) {
this.custPhone = custPhone;
}
@Override
public String toString() {
return "Customer [custId=" + custId + ", custName=" + custName + ", custSource=" + custSource
+ ", custIndustry=" + custIndustry + ", custLevel=" + custLevel + ", custAddress=" + custAddress
+ ", custPhone=" + custPhone + "]";
}
}
此时,向客户表中保存一个对象:Hibernate会自动创建一张hibernate_sequences表,存储下一个主键值,当初次获取主键值时,先查询hibernate_sequences,此时该表中没有数据,于是,向该表中插入一行初始数据,值为1,再把该表中的主键值+1.
- JPA注解的CRUD
- 保存
@Test
public void test1(){
Customer customer = new Customer();
customer.setCustName("蔚来汽车");
//获取JPA中实体管理器
EntityManager em = JPAUtils.getEntityManager();
//获取事务
EntityTransaction tx = em.getTransaction();
//开启事务
tx.begin();
//保存对象
em.persist(customer);
//提交事务
tx.commit();
//释放资源
em.close();
}
-
- 查询一个
/**
* 查询一个
*/
@Test
public void test2(){
//获取JPA中实体管理器
EntityManager em = JPAUtils.getEntityManager();
//获取事务
EntityTransaction tx = em.getTransaction();
//开启事务
tx.begin();
Customer customer = em.find(Customer.class, 1L);
System.out.println(customer);
//提交事务
tx.commit();
//释放资源
em.close();
}
JPA也是有缓存的:
/**
* 查询一个:缓存问题
*/
@Test
public void test2Cache(){
//获取JPA中实体管理器
EntityManager em = JPAUtils.getEntityManager();
//获取事务
EntityTransaction tx = em.getTransaction();
//开启事务
tx.begin();
Customer customer1 = em.find(Customer.class, 1L);
Customer customer2 = em.find(Customer.class, 1L);
System.out.println(customer1==customer2);
//提交事务
tx.commit();
//释放资源
em.close();
}
JPA中也是有延迟加载的:
/**
* 查询一个:延迟加载的问题
*/
@Test
public void test2Lazy(){
//获取JPA中实体管理器
EntityManager em = JPAUtils.getEntityManager();
//获取事务
EntityTransaction tx = em.getTransaction();
//开启事务
tx.begin();
Customer customer = em.getReference(Customer.class, 1L);//不发sql语句
System.out.println(customer);//真正要用的时候,才发送select语句
//提交事务
tx.commit();
//释放资源
em.close();
}
-
- 查询所有
/**
* 查询所有:利用JPQL来查询
* JPQL是Java Persistence Query Language,中文含义是Java持久化查询语言
sql中可以给表起别名,例如:select * from cst_customer c
JPQL语法规定:要给类加别名,例如select c from Customer c,表示查询表里有数的数据
*
*/
@Test
public void test3(){
//获取JPA中实体管理器
EntityManager em = JPAUtils.getEntityManager();
//获取事务
EntityTransaction tx = em.getTransaction();
//开启事务
tx.begin();
Query query = em.createQuery("select c from Customer c where c.custName like ?");
query.setParameter(1, "%张%");
List<Customer> list = query.getResultList();
for(Customer c:list){
System.out.println(c);
}
//提交事务
tx.commit();
//释放资源
em.close();
}
细节:JPA中的Query设置参数的位置是从1开始的。
-
- 更新
/**
* 修改
*/
@Test
public void update(){
//获取JPA中实体管理器
EntityManager em = JPAUtils.getEntityManager();
//获取事务
EntityTransaction tx = em.getTransaction();
//开启事务
tx.begin();
Customer customer = em.find(Customer.class, 1L);
customer.setCustName("腾讯科技");
//提交事务
tx.commit();//使用JPA中快照机制实现更新
//释放资源
em.close();
}
-
- 删除
/**
* 删除
*/
@Test
public void delete(){
//获取JPA中实体管理器
EntityManager em = JPAUtils.getEntityManager();
//获取事务
EntityTransaction tx = em.getTransaction();
//开启事务
tx.begin();
Customer customer = em.find(Customer.class, 2L);
em.remove(customer);
//提交事务
tx.commit();//使用JPA中快照机制实现更新
//释放资源
em.close();
}
- JPA多表映射
- 一对多关系映射
- 常用注解
- @OneToMany
- 常用注解
- 一对多关系映射
作用:
建立一对多的关系映射
属性:
targetEntityClass:指定多的多方的类的字节码
mappedBy:指定从表实体类中引用主表对象的名称。让主表(一方)放弃外键维护。
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
orphanRemoval:是否使用孤儿删除
-
-
-
- @ManyToOne
-
-
作用:
建立多对一的关系
属性:
targetEntityClass:指定一的一方实体类字节码
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
optional:关联是否可选。如果设置为false,则必须始终存在非空关系。
-
-
-
- @JoinColumn
-
-
作用:
用于定义主键字段和外键字段的对应关系。
属性:
name:指定外键字段的名称
referencedColumnName:指定引用主表的主键字段名称
unique:是否唯一。默认值不唯一
nullable:是否允许为空。默认值允许。
insertable:是否允许插入。默认值允许。
updatable:是否允许更新。默认值允许。
columnDefinition:列的定义信息。
-
-
- 配置代码
-
创建客户和联系人实体类,实体类的设计同之前的代码:
-
-
-
- 客户实体类的配置
-
-
/**
* 客户实体类
* @author kevin
*/
@Entity
@Table(name="cst_customer")
public class Customer implements Serializable{
private static final long serialVersionUID = 1L;
@Id
@Column(name="cust_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long custId;
@Column(name="cust_name")
private String custName;
@Column(name="cust_source")
private String custSource;
@Column(name="cust_industry")
private String custIndustry;
@Column(name="cust_level")
private String custLevel;
@Column(name="cust_address")
private String custAddress;
@Column(name="cust_phone")
private String custPhone;
//mappedBy属性:表示由多方(联系人)来维护主键,值就是多方(联系人)中一方的属性名
@OneToMany(mappedBy="customer")
private Set<LinkMan> linkMans = new HashSet<LinkMan>();
public Long getCustId() {
return custId;
}
public void setCustId(Long custId) {
this.custId = custId;
}
public String getCustName() {
return custName;
}
public void setCustName(String custName) {
this.custName = custName;
}
public String getCustSource() {
return custSource;
}
public void setCustSource(String custSource) {
this.custSource = custSource;
}
public String getCustIndustry() {
return custIndustry;
}
public void setCustIndustry(String custIndustry) {
this.custIndustry = custIndustry;
}
public String getCustLevel() {
return custLevel;
}
public void setCustLevel(String custLevel) {
this.custLevel = custLevel;
}
public String getCustAddress() {
return custAddress;
}
public void setCustAddress(String custAddress) {
this.custAddress = custAddress;
}
public String getCustPhone() {
return custPhone;
}
public void setCustPhone(String custPhone) {
this.custPhone = custPhone;
}
public Set<LinkMan> getLinkMans() {
return linkMans;
}
public void setLinkMans(Set<LinkMan> linkMans) {
this.linkMans = linkMans;
}
@Override
public String toString() {
return "Customer [custId=" + custId + ", custName=" + custName + ", custSource=" + custSource
+ ", custIndustry=" + custIndustry + ", custLevel=" + custLevel + ", custAddress=" + custAddress
+ ", custPhone=" + custPhone + "]";
}
}
-
-
-
- 联系人实体类的配置
-
-
/**
* 联系人实体类
* @author kevin
*/
@Entity
@Table(name="cst_linkman")
public class LinkMan implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Column(name="lkm_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long lkmId;
@Column(name="lkm_name")
private String lkmName;
@Column(name="lkm_gender")
private String lkmGender;
@Column(name="lkm_phone")
private String lkmPhone;
@Column(name="lkm_mobile")
private String lkmMobile;
@Column(name="lkm_email")
private String lkmEmail;
@Column(name="lkm_position")
private String lkmPosition;
@Column(name="lkm_memo")
private String lkmMemo;
//多对一关系映射:多个联系人对应一个客户
@ManyToOne
@JoinColumn(name="lkm_customer_id")
private Customer customer;
public Long getLkmId() {
return lkmId;
}
public void setLkmId(Long lkmId) {
this.lkmId = lkmId;
}
public String getLkmName() {
return lkmName;
}
public void setLkmName(String lkmName) {
this.lkmName = lkmName;
}
public String getLkmGender() {
return lkmGender;
}
public void setLkmGender(String lkmGender) {
this.lkmGender = lkmGender;
}
public String getLkmPhone() {
return lkmPhone;
}
public void setLkmPhone(String lkmPhone) {
this.lkmPhone = lkmPhone;
}
public String getLkmMobile() {
return lkmMobile;
}
public void setLkmMobile(String lkmMobile) {
this.lkmMobile = lkmMobile;
}
public String getLkmEmail() {
return lkmEmail;
}
public void setLkmEmail(String lkmEmail) {
this.lkmEmail = lkmEmail;
}
public String getLkmPosition() {
return lkmPosition;
}
public void setLkmPosition(String lkmPosition) {
this.lkmPosition = lkmPosition;
}
public String getLkmMemo() {
return lkmMemo;
}
public void setLkmMemo(String lkmMemo) {
this.lkmMemo = lkmMemo;
}
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
@Override
public String toString() {
return "LinkMan [lkmId=" + lkmId + ", lkmName=" + lkmName + ", lkmGender=" + lkmGender + ", lkmPhone="
+ lkmPhone + ", lkmMobile=" + lkmMobile + ", lkmEmail=" + lkmEmail + ", lkmPosition=" + lkmPosition
+ ", lkmMemo=" + lkmMemo + "]";
}
}
-
-
-
- 测试注解配置的正确性
-
-
在测试之前,先修改persistence.xml中的hibernte.hbm2ddl.auto的值为create;
在TestJPA中新建单元测试方法如下:
@Test
public void test2(){
EntityManager em = JPAUtils.getEntityManager();
}
-
- 多对多关系映射
创建用户和角色实体类,实体类的设计同之前的代码:
-
-
- 常用注解
- @ManyToMany
- 常用注解
-
作用:
用于映射多对多关系
属性:
cascade:配置级联操作。
fetch:配置是否采用延迟加载。
targetEntity:配置目标的实体类。映射多对多的时候不用写。
-
-
-
- @JoinTable
-
-
作用:
针对中间表的配置
属性:
nam:配置中间表的名称
joinColumns:中间表的外键字段关联当前实体类所对应表的主键字段
inverseJoinColumn:中间表的外键字段关联对方表的主键字段
-
-
-
- @JoinColumn
-
-
作用:
用于定义主键字段和外键字段的对应关系。
属性:
name:指定外键字段的名称
referencedColumnName:指定引用主表的主键字段名称
unique:是否唯一。默认值不唯一
nullable:是否允许为空。默认值允许。
insertable:是否允许插入。默认值允许。
updatable:是否允许更新。默认值允许。
columnDefinition:列的定义信息。
-
-
- 配置代码
- 用户实体类的配置
- 配置代码
-
/**
* 用户实体类
* @author kevin
*/
@Entity
@Table(name="sys_user")
public class SysUser implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Column(name="user_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long userId;
@Column(name="user_name")
private String userName;
@Column(name="user_memo")
private String userMemo;
//多对多关系映射
@ManyToMany
@JoinTable(name="user_role",joinColumns=@JoinColumn(name="user_id"),inverseJoinColumns=@JoinColumn(name="role_id"))
private Set<SysRole> roles = new HashSet<SysRole>();
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserMemo() {
return userMemo;
}
public void setUserMemo(String userMemo) {
this.userMemo = userMemo;
}
public Set<SysRole> getRoles() {
return roles;
}
public void setRoles(Set<SysRole> roles) {
this.roles = roles;
}
@Override
public String toString() {
return "SysUser [userId=" + userId + ", userName=" + userName + ", userMemo=" + userMemo + "]";
}
}
-
-
-
- 角色实体类的配置
-
-
/**
* 角色实体类
* @author kevin
*/
@Entity
@Table(name="sys_role")
public class SysRole implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Column(name="role_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long roleId;
@Column(name="role_name")
private String roleName;
@Column(name="role_memo")
private String roleMemo;
//一个角色对应多个用户
@ManyToMany(mappedBy="roles")
private Set<SysUser> users = new HashSet<SysUser>();
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long 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;
}
public Set<SysUser> getUsers() {
return users;
}
public void setUsers(Set<SysUser> users) {
this.users = users;
}
@Override
public String toString() {
return "SysRole [roleId=" + roleId + ", roleName=" + roleName + ", roleMemo=" + roleMemo + "]";
}
}
-
-
-
- 测试注解配置的正确性
-
-
在测试之前,先修改persistence.xml中的hibernte.hbm2ddl.auto的值为create;
在TestJPA中新建单元测试方法如下:
@Test
public void test2(){
EntityManager em = JPAUtils.getEntityManager();
}
小结:
@OneToMany
@ManyToOne
@JoinColumn
@ManyToMany
@JoinTable
- JPA多表操作
- 一对多关系的增删改操作
- 保存操作
- 一对多关系的增删改操作
我们先看一对多中的普通保存:
/**
* 一对多保存
* 建立双向关系
*/
@Test
public void test1(){
Customer customer = new Customer();
customer.setCustName("客户1");
LinkMan linkMan = new LinkMan();
linkMan.setLkmName("联系人1");
//建立客户和联系人的双向关系,不会有多余的update语句,因为我们在一方(客户)加了mappedBy,放弃了外键维护权
customer.getLinkMans().add(linkMan);
linkMan.setCustomer(customer);
EntityManager em = JPAUtils.getEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
em.persist(customer);
em.persist(linkMan);
tx.commit();
em.close();
}
如果想级联保存,在Customer实体类的@OneToMany注解中加上级联保存属性:
//一个客户有多个联系人
//mappedBy属性:表示由多方(联系人)来维护主键,值就是多方(联系人)中一方的属性名
@OneToMany(mappedBy="customer",cascade={ CascadeType.PERSIST})
修改Customer实体类代码如下:
@Entity
@Table(name="cst_customer")
public class Customer implements Serializable{
private static final long serialVersionUID = 1L;
@Id
@Column(name="cust_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long custId;
@Column(name="cust_name")
private String custName;
@Column(name="cust_source")
private String custSource;
@Column(name="cust_industry")
private String custIndustry;
@Column(name="cust_level")
private String custLevel;
@Column(name="cust_address")
private String custAddress;
@Column(name="cust_phone")
private String custPhone;
//mappedBy属性:表示由多方(联系人)来维护主键,值就是多方(联系人)中一方的属性名
@OneToMany(mappedBy="customer",cascade=CascadeType.PERSIST)
private Set<LinkMan> linkMans = new HashSet<LinkMan>();
public Long getCustId() {
return custId;
}
public void setCustId(Long custId) {
this.custId = custId;
}
public String getCustName() {
return custName;
}
public void setCustName(String custName) {
this.custName = custName;
}
public String getCustSource() {
return custSource;
}
public void setCustSource(String custSource) {
this.custSource = custSource;
}
public String getCustIndustry() {
return custIndustry;
}
public void setCustIndustry(String custIndustry) {
this.custIndustry = custIndustry;
}
public String getCustLevel() {
return custLevel;
}
public void setCustLevel(String custLevel) {
this.custLevel = custLevel;
}
public String getCustAddress() {
return custAddress;
}
public void setCustAddress(String custAddress) {
this.custAddress = custAddress;
}
public String getCustPhone() {
return custPhone;
}
public void setCustPhone(String custPhone) {
this.custPhone = custPhone;
}
public Set<LinkMan> getLinkMans() {
return linkMans;
}
public void setLinkMans(Set<LinkMan> linkMans) {
this.linkMans = linkMans;
}
@Override
public String toString() {
return "Customer [custId=" + custId + ", custName=" + custName + ", custSource=" + custSource
+ ", custIndustry=" + custIndustry + ", custLevel=" + custLevel + ", custAddress=" + custAddress
+ ", custPhone=" + custPhone + "]";
}
}
修改保存测试代码:只需要保存客户即可:
/**
* 一对多级联保存:保存客户级联保存联系人
* 在Customer实体类的OneToMany注解中加cascade=CascadeType.PERSIST
*
*/
@Test
public void test2(){
Customer customer = new Customer();
customer.setCustName("客户1");
LinkMan linkMan = new LinkMan();
linkMan.setLkmName("联系人1");
//建立客户和联系人的双向关系,不会有多余的update语句,因为我们在一方(客户)加了mappedBy,放弃了外键维护权
customer.getLinkMans().add(linkMan);
linkMan.setCustomer(customer);
EntityManager em = JPAUtils.getEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
em.persist(customer);
tx.commit();
em.close();
}
-
-
- 删除操作
-
这里直接演示删除客户级联删除联系人。
如果想级联删除,可以在Customer实体类这边配置级联删除
提示:如果想配置多个级联操作,可以这样来配置:cascade={CascadeType.PERSIST,CascadeType.REMOVE}
修改Customer实体类如下:
@Entity
@Table(name="cst_customer")
public class Customer implements Serializable{
private static final long serialVersionUID = 1L;
@Id
@Column(name="cust_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long custId;
@Column(name="cust_name")
private String custName;
@Column(name="cust_source")
private String custSource;
@Column(name="cust_industry")
private String custIndustry;
@Column(name="cust_level")
private String custLevel;
@Column(name="cust_address")
private String custAddress;
@Column(name="cust_phone")
private String custPhone;
//mappedBy:指定一方放弃外键维护权
@OneToMany(mappedBy="customer",cascade={CascadeType.PERSIST,CascadeType.REMOVE})
private Set<LinkMan> linkMans = new HashSet<LinkMan>();
public Long getCustId() {
return custId;
}
public void setCustId(Long custId) {
this.custId = custId;
}
public String getCustName() {
return custName;
}
public void setCustName(String custName) {
this.custName = custName;
}
public String getCustSource() {
return custSource;
}
public void setCustSource(String custSource) {
this.custSource = custSource;
}
public String getCustIndustry() {
return custIndustry;
}
public void setCustIndustry(String custIndustry) {
this.custIndustry = custIndustry;
}
public String getCustLevel() {
return custLevel;
}
public void setCustLevel(String custLevel) {
this.custLevel = custLevel;
}
public String getCustAddress() {
return custAddress;
}
public void setCustAddress(String custAddress) {
this.custAddress = custAddress;
}
public String getCustPhone() {
return custPhone;
}
public void setCustPhone(String custPhone) {
this.custPhone = custPhone;
}
public Set<LinkMan> getLinkMans() {
return linkMans;
}
public void setLinkMans(Set<LinkMan> linkMans) {
this.linkMans = linkMans;
}
@Override
public String toString() {
return "Customer [custId=" + custId + ", custName=" + custName + ", custSource=" + custSource
+ ", custIndustry=" + custIndustry + ", custLevel=" + custLevel + ", custAddress=" + custAddress
+ ", custPhone=" + custPhone + "]";
}
}
删除测试代码如下:
/**
* 一对多级联删除:删除客户,级联删除联系人
*
*/
@Test
public void test3(){
EntityManager em = JPAUtils.getEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
Customer customer = em.find(Customer.class, 1L);
em.remove(customer);
tx.commit();
em.close();
}
-
-
- 修改操作
-
/**
* 一对多的更新
* 需求:让2号联系人属于2号客户
*/
@Test
public void test6(){
EntityManager em = JPAUtils.getEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
//先查询id为2的联系人
LinkMan linkMan = em.find(LinkMan.class, 2L);
//再查询id为2的客户
Customer customer = em.find(Customer.class, 2L);
//重新建立关系
linkMan.setCustomer(customer);
tx.commit();
em.close();
}
-
- 多对多关系的增删改操作
- 保存操作
- 多对多关系的增删改操作
先看多对多中的普通保存:
@Test
public void test1(){
SysUser user1 = new SysUser();
user1.setUserName("张三");
SysUser user2 = new SysUser();
user2.setUserName("李四");
SysRole role1 = new SysRole();
role1.setRoleName("角色1");
SysRole role2 = new SysRole();
role2.setRoleName("角色2");
SysRole role3 = new SysRole();
role3.setRoleName("角色3");
user1.getRoles().add(role1);
user1.getRoles().add(role2);
role1.getUsers().add(user1);
role2.getUsers().add(user1);
user2.getRoles().add(role2);
user2.getRoles().add(role3);
role2.getUsers().add(user2);
role3.getUsers().add(user2);
EntityManager em = JPAUtils.getEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
em.persist(user1);
em.persist(user2);
em.persist(role1);
em.persist(role2);
em.persist(role3);
tx.commit();
em.close();
}
如果想级联保存,在User实体类中设置级联保存:
@Entity
@Table(name = "sys_user")
public class SysUser implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@Column(name="user_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long userId;
@Column(name="user_name")
private String userName;
@Column(name="user_memo")
private String userMemo;
//多对多关系映射
@ManyToMany(cascade={CascadeType.PERSIST})
@JoinTable(name="user_role",joinColumns=@JoinColumn(name="user_id"),inverseJoinColumns=@JoinColumn(name="role_id"))
private Set<SysRole> roles = new HashSet<SysRole>();
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserMemo() {
return userMemo;
}
public void setUserMemo(String userMemo) {
this.userMemo = userMemo;
}
public Set<SysRole> getRoles() {
return roles;
}
public void setRoles(Set<SysRole> roles) {
this.roles = roles;
}
@Override
public String toString() {
return "SysUser [userId=" + userId + ", userName=" + userName + ", userMemo=" + userMemo + "]";
}
}
因为设置了级联保存,所以只需要保存用户即可:
@Test
public void test1(){
SysUser user1 = new SysUser();
user1.setUserName("用户1");
SysUser user2 = new SysUser();
user2.setUserName("用户2");
SysRole role1 = new SysRole();
role1.setRoleName("角色1");
SysRole role2 = new SysRole();
role2.setRoleName("角色2");
SysRole role3 = new SysRole();
role3.setRoleName("角色3");
user1.getRoles().add(role1);
user1.getRoles().add(role2);
user2.getRoles().add(role2);
user2.getRoles().add(role3);
role1.getUsers().add(user1);
role2.getUsers().add(user1);
role2.getUsers().add(user2);
role3.getUsers().add(user2);
//获取EntityManager
EntityManager em = JPAUtils.getEntityManager();
//获取事务
EntityTransaction tx = em.getTransaction();
//开始事务
tx.begin();
//保存操作
em.persist(user1);
em.persist(user2);
/*em.persist(role1);
em.persist(role2);
em.persist(role3);*/
//提交事务
tx.commit();
//释放资源
em.close();
}
-
-
- 删除操作
-
先看直接删除用户的情况:
/**
* 测试删除:直接删除用户
* 结果:先删除中间表中与该用户相关的信息,再删除用户
*/
@Test
public void test2(){
EntityManager em = JPAUtils.getEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
SysUser user = em.find(SysUser.class, 1L);
em.remove(user);
tx.commit();
em.close();
}
如果想级联删除,可以在用户一方配置级联删除:
//一个用户对应多个角色
@ManyToMany(cascade=CascadeType.REMOVE)
@JoinTable(name="user_role",joinColumns=@JoinColumn(name="user_id"),inverseJoinColumns=@JoinColumn(name="role_id"))
private Set<SysRole> roles = new HashSet<SysRole>();
/**
* 测试多对多级联删除:删除用户级联删除角色
*/
@Test
public void test3(){
EntityManager em = JPAUtils.getEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
SysUser user = em.find(SysUser.class, 1L);
em.remove(user);
tx.commit();
em.close();
}
测试发现:报错,原因同xml配置。所以,在多对多中一般不配置级联删除。
-
-
- 修改操作
-
/**
* 多对多的更新
* 需求:把用户1中的角色1换成角色3
*/
@Test
public void test5(){
EntityManager em = JPAUtils.getEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
//先查询id为1的用户
SysUser user = em.find(SysUser.class, 1L);
//再查询id为1的角色
SysRole role1 = em.find(SysRole.class, 1L);
//再查询id为3的角色
SysRole role3 = em.find(SysRole.class, 3L);
//先移除角色1
user.getRoles().remove(role1);
//在添加角色3
user.getRoles().add(role3);
tx.commit();
em.close();
}
-
- JPA里的对象导航查询
明确:导航查询的意义和xml的意义一样
-
-
- 查询一方时,延迟加载多方数据
-
/**
* 查询客户,也要把客户对应的联系人查询出来
*/
@Test
public void test1(){
//获取EntityManager
EntityManager em = JPAUtils.getEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
//查询客户时,延迟加载联系人
Customer customer = em.find(Customer.class, 1L);
System.out.println(customer.getCustName());
Set<LinkMan> linkMans = customer.getLinkMans();
for (LinkMan linkMan : linkMans) {
System.out.println(linkMan);
}
tx.commit();
em.close();
}
观察测试结果发现,查询一方延迟加载多方数据,这点和xml配置是一样的。测试结果如下:
-
-
- 查询多方,立即加载一方数据
-
/**
* 查询联系人,也要把联系人所属的客户查询出来
*/
@Test
public void test2(){
//获取EntityManager
EntityManager em = JPAUtils.getEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
//查询联系人时,会立即加载客户
LinkMan linkMan = em.find(LinkMan.class, 1L);
System.out.println(linkMan.getLkmName());
Customer customer = linkMan.getCustomer();
System.out.println(customer);
tx.commit();
em.close();
}
观察测试结果发现:查询多方立即加载一方数据,这点和xml正好是相反的。我们也不需要改变这种情况,因为我们就是需要查询多方立即加载一方数据。测试结果如下:
提示:如果想改变默认的抓取策略,可以给OneToMany、ManyToOne注解加fetch属性:
fetch属性可以取如下两个值:
1、EAGER:表示立即加载;
2、LAZY:表示延迟加载;
在注解里,默认情况下,查询一方是延迟加载多方,如果想改变这种情况,可以在OneToMany中加fetch属性,如下:
@Entity
@Table(name="cst_customer")
public class Customer implements Serializable{
private static final long serialVersionUID = 1L;
@Id
@Column(name="cust_id")
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long custId;
@Column(name="cust_name")
private String custName;
@Column(name="cust_source")
private String custSource;
@Column(name="cust_industry")
private String custIndustry;
@Column(name="cust_level")
private String custLevel;
@Column(name="cust_address")
private String custAddress;
@Column(name="cust_phone")
private String custPhone;
//mappedBy:指定一方放弃外键维护权
@OneToMany(mappedBy="customer",cascade={CascadeType.PERSIST,CascadeType.REMOVE},fetch=FetchType.EAGER)
private Set<LinkMan> linkMans = new HashSet<LinkMan>();
public Long getCustId() {
return custId;
}
public void setCustId(Long custId) {
this.custId = custId;
}
public String getCustName() {
return custName;
}
public void setCustName(String custName) {
this.custName = custName;
}
public String getCustSource() {
return custSource;
}
public void setCustSource(String custSource) {
this.custSource = custSource;
}
public String getCustIndustry() {
return custIndustry;
}
public void setCustIndustry(String custIndustry) {
this.custIndustry = custIndustry;
}
public String getCustLevel() {
return custLevel;
}
public void setCustLevel(String custLevel) {
this.custLevel = custLevel;
}
public String getCustAddress() {
return custAddress;
}
public void setCustAddress(String custAddress) {
this.custAddress = custAddress;
}
public String getCustPhone() {
return custPhone;
}
public void setCustPhone(String custPhone) {
this.custPhone = custPhone;
}
public Set<LinkMan> getLinkMans() {
return linkMans;
}
public void setLinkMans(Set<LinkMan> linkMans) {
this.linkMans = linkMans;
}
@Override
public String toString() {
return "Customer [custId=" + custId + ", custName=" + custName + ", custSource=" + custSource
+ ", custIndustry=" + custIndustry + ", custLevel=" + custLevel + ", custAddress=" + custAddress
+ ", custPhone=" + custPhone + "]";
}
}
修改完之后,再次运行如下代码:
/**
* 在JPA中:查询一方延迟加载多方
*/
@Test
public void test1() {
// 获取EntityManager
EntityManager em = JPAUtils.getEntityManager();
// 获取事务
EntityTransaction tx = em.getTransaction();
// 开启事务
tx.begin();
Customer customer = em.find(Customer.class, 1L);
System.out.println(customer.getCustName());
Set<LinkMan> linkMans = customer.getLinkMans();
for (LinkMan linkMan : linkMans) {
System.out.println(linkMan);
}
// 提交事务
tx.commit();
// 释放资源
em.close();
}
测试结果表明:查询一方会;立即加载多方数据。但是,一般也没有改变fetch的默认值。测试结果如下:
多对多:看成两个一对多的组合