学习尚硅谷JPA教程
JPA是一个用于对象持久化的 API , 是一个ORM规范
换句话说,为啥只用JPA而不用Hibernate呢?这是因为hibernate对jpa的支持,不是另提供了一套专用于jpa的注解。一些重要的注解如Column, OneToMany等,hibernate没有提供,这说明jpa的注解已经是hibernate 的核心,hibernate只提供了一些补充,而不是两套注解。
hibernate不只实现了JPA的整套功能,还在此基础上增加了一些额外的功能,若JPA提供的功能够我们用了,我们就可以直接用JPA,而且以后切换到另外实现类也很方便,若用Hibernate的话,需要学习一写它的用法,增加了学习成本,而且不利于切换到JPA其他实现版本上去
尚硅谷JPA教程中使用的是eclipse , 非Maven项目 , 所以是手动导入
JPA主要是基于注解开发 , 有一个核心配置类persistence.xml
JPA 规范要求在类路径的 META-INF 目录下放置
如果在IDEA中,resources文件夹下,也就是资源文件夹下,创建META-INF文件夹,在该文件夹下创建persistence.xml文件,添加以下配置。
<persistence version="2.0"
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">
<persistence-unit name="jpa-1" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistenceprovider>
<class>com.atguigu.jpa.helloworld.Customerclass>
<class>com.atguigu.jpa.helloworld.Orderclass>
<class>com.atguigu.jpa.helloworld.Departmentclass>
<class>com.atguigu.jpa.helloworld.Managerclass>
<class>com.atguigu.jpa.helloworld.Itemclass>
<class>com.atguigu.jpa.helloworld.Categoryclass>
<shared-cache-mode>ENABLE_SELECTIVEshared-cache-mode>
<properties>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql:///jpa"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="javax.persistence.jdbc.password" value="1230"/>
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="hibernate.cache.use_second_level_cache" value="true"/>
<property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.EhCacheRegionFactory"/>
<property name="hibernate.cache.use_query_cache" value="true"/>
properties>
persistence-unit>
persistence>
//数据库中要创建的对应的表名
@Table(name="JPA_CUTOMERS")
//告诉JPA这是一个需要持久化的类
@Entity
public class Customer {
private Integer id;
private String lastName;
//@Column: 指定数据库中对应的列名 , 如果数据库列名 和 实体字段名一样就可以省略不写
@Column(name= "ID")
//@GeneratedValue 用于标注主键的生成策略,通过strategy 属性指定。 GenerationType.AUTO: 系统自动创建
@GeneratedValue(strategy=GenerationType.AUTO)
//@Id 标注用于声明一个实体类的属性映射为数据库的主键列
@Id
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Column(name="LAST_NAME",length=50,nullable=false)
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
参考文章:JPA - Persistence与EntityManagerFactory
或者创建测试类 将注解跑起来
public class Main {
public static void main(String[] args) {
//1. 创建 EntitymanagerFactory 接口对象
//persistence 配置文件
String persistenceUnitName = "jpa-1";
Map<String, Object> properites = new HashMap<String, Object>();
properites.put("hibernate.show_sql", true);
//Persistence 类是用于获取 EntityManagerFactory 实例。该类包含一个名为 createEntityManagerFactory 的 静态方法 。
EntityManagerFactory entityManagerFactory =
//Persistence.createEntityManagerFactory(persistenceUnitName);
Persistence.createEntityManagerFactory(persistenceUnitName, properites);
//2. 创建 EntityManager实例. 类似于 Hibernate 的 SessionFactory
//EntityManagerFactory 接口主要用来创建 EntityManager 实例。
EntityManager entityManager = entityManagerFactory.createEntityManager();
//3. 开启事务
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
//4. 进行持久化操作
Customer customer = new Customer();
customer.setAge(12);
customer.setEmail("[email protected]");
customer.setLastName("Tom");
customer.setBirth(new Date());
customer.setCreatedTime(new Date());
//persist方法类似于Hibernate save方法,但是存在一个不同点:
//在数据库主键自增情况下:若提前设置id属性值,JPA EntityManager 的 persist方法将抛出异常,而Hibernate Session的 save 方法可以执行,将忽略提前设置的id值
entityManager.persist(customer);
//5. 提交事务
transaction.commit();
//6. 关闭 EntityManager
entityManager.close();
//7. 关闭 EntityManagerFactory
entityManagerFactory.close();
}
}
打开数据库 , 发现数据库中自动创建了一张表和对应的字段 , 控制台输出了 hibernate 自动生成的 SQL 语句
@Entity说明这个class是实体类,并且使用默认的orm规则,即class名对应数据库表中表名,class字段名即表中的字段名。
(如果想改变这种默认的orm规则,就要使用@Table来改变class名与数据库中表名的映射规则,@Column来改变class中字段名与db中表的字段名的映射规则)
元数据属性说明:
下面的代码说明,Customer类对应数据库中的Customer表,其中name为可选,缺省类名即表名!
public class Customer {
... }
@Id 标注用于声明一个实体类的属性映射为数据库的主键列。该属性通常置于属性声明语句之前,可与声明语句同行,也可写在单独行上。
@Id标注也可置于属性的getter方法之前。
//@Column: 指定数据库中对应的列名 , 如果数据库列名 和 实体字段名一样就可以省略不写
@Column(name= "ID")
//@GeneratedValue 用于标注主键的生成策略,通过strategy 属性指定。 GenerationType.AUTO: 系统自动创建
@GeneratedValue(strategy=GenerationType.AUTO)
//@Id 标注用于声明一个实体类的属性映射为数据库的主键列
@Id
public Integer getId() {
return id;
}
用来将实体类与数据库表映射起来 , 特别是类名与表民不同的情况
Table用来定义entity主表的name,catalog,schema等属性。
元数据属性说明:
@Table(name="CUST")
public class Customer {
... }
如果自己不手动加 , 系统会默认已经加了@Basic注解
@Basic 表示一个简单的属性到数据库表的字段的映射,对于没有任何标注的 getXxxx() 方法,默认即为@Basic
当实体的属性与其映射的数据库表的列不同名时需要使用@Column 标注说明
Column元数据定义了映射到数据库的列的所有属性:列名,是否唯一,是否允许为空,是否允许更新等。
元数据属性说明:
public class Person {
@Column(name = "PERSONNAME", unique = true, nullable = false, updatable = true)
private String name;
@Column(name = "PHOTO", columnDefinition = "BLOB NOT NULL", secondaryTable="PER_PHOTO")
private byte[] picture;
@GeneratedValue 用于标注主键的生成策略,通过 strategy 属性指定。默认情况下,JPA 自动选择一个最适合底层数据库的主键生成策略:SqlServer 对应 identity,MySQL 对应 auto increment。
在 javax.persistence.GenerationType 中定义了以下几种可供选择的策略:
默认生成的时间属性是datatime 类型 , 就是 年月日时分秒都有的
在核心的 Java API 中并没有定义 Date 类型的精度(temporal precision). 而在数据库中,表示 Date 类型的数据有 DATE, TIME, 和 TIMESTAMP 三种精度(即单纯的日期,时间,或者两者 兼备). 在进行属性映射时可使用@Temporal注解来调整精度.
// TIMESTAMP : 年月日 时分秒
@Temporal(TemporalType.TIMESTAMP)
public Date getCreatedTime() {
return createdTime;
}
//DATE : 年月日
@Temporal(TemporalType.DATE)
public Date getBirth() {
return birth;
}
不需要为他生成数据表字段的时候
表示该属性并非一个到数据库表的字段的映射,ORM框架将忽略该属性.
如果一个属性并非数据库表的字段映射,就务必将其标示为@Transient,否则,ORM框架默认其注解为@Basic
//工具方法. 不需要映射为数据表的一列.
@Transient
public String getInfo(){
return "lastName: " + lastName + ", email: " + email;
}
Persistence 类是用于获取 EntityManagerFactory 实例。该类包含一个名为 createEntityManagerFactory 的 静态方法 。
createEntityManagerFactory 方法有如下两个重载版本。
//persistence 配置文件
String persistenceUnitName = "jpa-1";
Map<String, Object> properites = new HashMap<String, Object>();
properites.put("hibernate.show_sql", true);
EntityManagerFactory entityManagerFactory =
Persistence.createEntityManagerFactory(persistenceUnitName);
//properites是Map类型 , 用于设置JPA相关的属性
EntityManagerFactory entityManagerFactory =
Persistence.createEntityManagerFactory(persistenceUnitName, properites);
EntityManagerFactory 接口主要用来创建 EntityManager 实例。该接口约定了如下4个方法:
//2. 创建 EntityManager实例. 类似于 Hibernate 的 SessionFactory
//EntityManagerFactory 接口主要用来创建 EntityManager 实例。
EntityManager entityManager = entityManagerFactory.createEntityManager();
//7. 关闭 EntityManagerFactory
entityManagerFactory.close();
在 JPA 规范中, EntityManager 是完成持久化操作的核心对象。实体作为普通 Java 对象,只有在调用 EntityManager 将其持久化后才会变成持久化对象。EntityManager 对象在一组实体类与底层数据源之间进行 O/R 映射的管理。它可以用来管理和更新 Entity Bean, 根椐主键查找 Entity Bean, 还可以通过JPQL语句查询实体。
实体的状态:
select查询
执行select语句 , 根据主键值 , 查找对应的对象类型的表中的数据
SELECT * FROM 表名 where id = ""
find (Class entityClass,Object primaryKey):返回指定的 OID 对应的实体类对象,如果这个实体存在于当前的持久化环境,则返回一个被缓存的对象;否则会创建一个新的 Entity, 并加载数据库中相关信息;若 OID 不存在于数据库中,则返回一个 null。第一个参数为被查询的实体类类型,第二个参数为待查找实体的主键值。
@Test
public void testFind() {
Customer customer = entityManager.find(Customer.class, 1);
System.out.println("-------------------------------------");
System.out.println(customer);
}
!!! 在调用find方法的时候 , 使用customer 之前就已经执行了sql语句
select查询
若实体Bean不存在,则抛出javax.persistence.EntityNotFoundException,另,不保 证 实体Bean 已被初始化
getReference (Class entityClass,Object primaryKey):与find()方法类似,不同的是:如果缓存中不存在指定的 Entity, EntityManager 会创建一个 Entity 类的代理对象,但是不会立即加载数据库中的信息,只有第一次真正使用此 Entity 的属性才加载,所以如果此 OID 在数据库不存在,getReference() 不会返回 null 值, 而是抛出EntityNotFoundException
代理对象可能会出现懒加载异常的问题
@Test
public void testGetReference(){
Customer customer = entityManager.getReference(Customer.class, 1);
System.out.println("-------------------------------------");
System.out.println(customer);
}
!!! 一开始调用getReference 方法只是返回了一个customer 的代理 , 只有当实际使用到customer 对象的时候才进行初始化, 才去执行sql语句.
save存储 , 将对象持久化到数据库中 ,不能写ID
persist (Object entity):用于将新创建的 Entity 纳入到 EntityManager 的管理。该方法执行后,传入 persist() 方法的 Entity 对象转换成持久化状态。
//类似于 hibernate 的 save 方法. 使对象由临时状态变为持久化状态.
//和 hibernate 的 save 方法的不同之处: 若对象有 id, 则不能执行 insert 操作, 而会抛出异常.
@Test
public void testPersistence(){
Customer customer = new Customer();
customer.setAge(15);
customer.setBirth(new Date());
customer.setCreatedTime(new Date());
customer.setEmail("[email protected]");
customer.setLastName("BB");
customer.setId(100);
entityManager.persist(customer);
System.out.println(customer.getId());
}
delete 根据 id 删除数据 , 不能是游离对象
remove (Object entity):删除实例。如果实例是被管理的,即与数据库实体记录关联,则同时会删除关联的数据库记录。
//类似于 hibernate 中 Session 的 delete 方法. 把对象对应的记录从数据库中移除
//但注意: 该方法只能移除 持久化 对象. 而 hibernate 的 delete 方法实际上还可以移除 游离对象.
@Test
public void testRemove(){
// Customer customer = new Customer();
// customer.setId(2); //游离对象
Customer customer = entityManager.find(Customer.class, 2);
entityManager.remove(customer);
}
!!!删除的时候 customer 必须包含全部数据 , 不能只有id
merge (T entity):merge() 用于处理 Entity 的同步。即数据库的插入和更新操作
/**
* 总的来说: 类似于 hibernate Session 的 saveOrUpdate 方法.
*/
//1. 若传入的是一个临时对象
//会创建一个新的对象, 把临时对象的属性复制到新的对象中, 然后对新的对象执行持久化操作. 所以
//新的对象中有 id, 但以前的临时对象中没有 id.
@Test
public void testMerge1(){
Customer customer = new Customer();
customer.setAge(18);
customer.setBirth(new Date());
customer.setCreatedTime(new Date());
customer.setEmail("[email protected]");
customer.setLastName("CC");
Customer customer2 = entityManager.merge(customer); //持久化新的对象
System.out.println("customer#id:" + customer.getId()); //null
System.out.println("customer2#id:" + customer2.getId());
}
//若传入的是一个游离对象, 即传入的对象有 OID.
//1. 若在 EntityManager 缓存中没有该对象
//2. 若在数据库中也没有对应的记录
//3. JPA 会创建一个新的对象, 然后把当前游离对象的属性复制到新创建的对象中
//4. 对新创建的对象执行 insert 操作.
@Test
public void testMerge2(){
Customer customer = new Customer();
customer.setAge(18);
customer.setBirth(new Date());
customer.setCreatedTime(new Date());
customer.setEmail("[email protected]");
customer.setLastName("DD");
customer.setId(100);
Customer customer2 = entityManager.merge(customer);
System.out.println("customer#id:" + customer.getId());
System.out.println("customer2#id:" + customer2.getId());
}
//若传入的是一个游离对象, 即传入的对象有 OID.
//1. 若在 EntityManager 缓存中没有该对象
//2. 若在数据库中也有对应的记录
//3. JPA 会查询对应的记录, 然后返回该记录对一个的对象, 再然后会把游离对象的属性复制到查询到的对象中.
//4. 对查询到的对象执行 update 操作.
@Test
public void testMerge3(){
Customer customer = new Customer();
customer.setAge(18);
customer.setBirth(new Date());
customer.setCreatedTime(new Date());
customer.setEmail("[email protected]");
customer.setLastName("EE");
customer.setId(4);
Customer customer2 = entityManager.merge(customer);
System.out.println(customer == customer2); //false
}
//若传入的是一个游离对象, 即传入的对象有 OID.
//1. 若在 EntityManager 缓存中有对应的对象
//2. JPA 会把游离对象的属性复制到查询到EntityManager 缓存中的对象中.
//3. EntityManager 缓存中的对象执行 UPDATE.
@Test
public void testMerge4(){
Customer customer = new Customer();
customer.setAge(18);
customer.setBirth(new Date());
customer.setCreatedTime(new Date());
customer.setEmail("[email protected]");
customer.setLastName("DD");
customer.setId(4);
Customer customer2 = entityManager.find(Customer.class, 4);
entityManager.merge(customer);
System.out.println(customer == customer2); //false
}
强制发送sql语句 , 使数据表中的数据与 内存中的数据保持一致
flush ():同步持久上下文环境,即将持久上下文环境的所有未保存实体的状态信息保存到数据库中。
setFlushMode (FlushModeType flushMode):设置持久上下文环境的Flush模式。参数可以取2个枚举
getFlushMode ():获取持久上下文环境的Flush模式。返回FlushModeType类的枚举值。
/**
* 同 hibernate 中 Session 的 flush 方法.
*/
@Test
public void testFlush(){
Customer customer = entityManager.find(Customer.class, 1);
System.out.println(customer);
customer.setLastName("AA");
entityManager.flush();
}
解释:
如果没有flush , 那么所有的sql 操作都要等 提交事务 commit之后再执行 , 然后数据库跟着改变
如果有flush , 那么在调用flush之后就会执行sql语句 , 内存中数据已经改变了 , 但是 因为没有提交事务 , 所以数据库中的数据还没有改变 , 必须等commit提交之后数据库才会进行改变
refresh (Object entity):用数据库实体记录的值更新实体对象的状态,即更新实例的属性值。
clear ():清除持久上下文环境,断开所有关联的实体。如果这时还有未提交的更新则会被撤消。
contains (Object entity):判断一个实例是否属于当前持久上下文环境管理的实体。
isOpen ():判断当前的实体管理器是否是打开状态。
getTransaction ():返回资源层的事务对象。EntityTransaction实例可以用于开始和提交多个事务。
close ():关闭实体管理器。之后若调用实体管理器实例的方法或其派生的查询对象的方法都将抛
llegalstateException 异常,除了getTransaction 和 isOpen方法(返回 false)。不过,当与实体管理器关联的事务处于活动状态时,调用 close 方法后持久上下文将仍处于被管理状态,直到事务完成。
createQuery (String qlString):创建一个查询对象。
createNamedQuery (String name):根据命名的查询语句块创建查询对象。参数为命名的查询语句。
createNativeQuery (String sqlString):使用标准 SQL语句创建查询对象。参数为标准SQL语句字符串。
createNativeQuery (String sqls, String resultSetMapping):使用标准SQL语句创建查询对象,并指定返回结果集 Map的 名称。
EntityTransaction 接口用来管理资源层实体管理器的事务操作。通过调用实体管理器的getTransaction方法 获得其实例。
注解 | 作用 |
---|---|
@Entity | 标注实体 |
@Id | 标注主键 |
@Table | 实体类与数据表映射 |
@Basic | 实体属性与数据表字段映射 |
@Column | 实体属性与数据表字段不一样的时候 |
@GeneratedValue | 主键的生成策略 |
@Temporal | dataTime类型 , 年月日时分秒 |
Transient | 不需要生成数据表字段 |
API 接口与方法 | 作用 |
---|---|
Persistence | 用来获取 EntityManagerFactory 实例 |
EntityManagerFactory | 用来创建 EntityManager 实例 |
find | select语句 ,根据主键查询数据 ,返回实体类对象 , 先执行sql |
getReference | select语句 ,根据主键查询数据 ,返回代理对象 , 使用后再执行sql |
persist | insert语句, 存储对象进数据库 , 不能写ID |
remove | delete 根据 id 删除数据 , 不能是游离对象 |
举例: 订单与客户的关系 , 一个客户有多个订单 , 但是一个订单只能对应一个客户
@Table(name="JPA_ORDERS") //对应的数据表名
@Entity //表明是实体
public class Order {
private Integer id;
private String orderName;
//订单对应的用户
private Customer customer;
@GeneratedValue //生成主键的策略 : 默认
@Id //主键
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Column(name="ORDER_NAME") //列名与字段不同的情况
public String getOrderName() {
return orderName;
}
public void setOrderName(String orderName) {
this.orderName = orderName;
}
//映射单向 n-1 的关联关系
//使用 @ManyToOne 来映射多对一的关联关系
//使用 @JoinColumn 来映射外键.
//可使用 @ManyToOne 的 fetch 属性来修改默认的关联属性的加载策略
@JoinColumn(name="CUSTOMER_ID")
@ManyToOne(fetch=FetchType.LAZY) //LAZY表示懒加载
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
}
插入数据 测试
如果先保存 1 的一端 , 就只会执行 3 遍 插入语句
如果先保存 多 的一端 , 就会执行 3遍插入 , 再执行一遍更新
/**
* 保存多对一时, 建议先保存 1 的一端, 后保存 n 的一端, 这样不会多出额外的 UPDATE 语句.
*/
@Test
public void testManyToOnePersist(){
Customer customer = new Customer();
customer.setAge(18);
customer.setBirth(new Date());
customer.setCreatedTime(new Date());
customer.setEmail("[email protected]");
customer.setLastName("GG");
Order order1 = new Order();
order1.setOrderName("G-GG-1");
Order order2 = new Order();
order2.setOrderName("G-GG-2");
//设置关联关系
order1.setCustomer(customer);
order2.setCustomer(customer);
//执行保存操作
entityManager.persist(customer); //1的一方
entityManager.persist(order1); //多的一方
entityManager.persist(order2); //多的一方
}
取出数据 测试
//默认情况下, 使用左外连接的方式来获取 n 的一端的对象和其关联的 1 的一端的对象.
//可使用 @ManyToOne 的 fetch 属性来修改默认的关联属性的加载策略
//如果设置@ManyToOne(fetch=FetchType.LAZY) 懒加载策略 , 那么 会 先执行 order的sql然后输出 , 载执行 Customer的sql然后输出
@Test
public void testManyToOneFind(){
Order order = entityManager.find(Order.class, 1);
System.out.println(order.getOrderName());
System.out.println(order.getCustomer().getLastName());
}
删除数据 测试
//不能直接删除 1 的一端, 因为有外键约束. 会报错
//先删除 多 的一端 , 然后删除 1 的一端
@Test
public void testManyToOneRemove(){
// Order order = entityManager.find(Order.class, 1);
// entityManager.remove(order);
Customer customer = entityManager.find(Customer.class, 7);
entityManager.remove(customer); //失败 , 外键约束
}
public class Customer {
private Set<Order> orders = new HashSet<>();
//映射单向 1-n 的关联关系
//使用 @OneToMany 来映射 1-n 的关联关系
//使用 @JoinColumn 来映射外键列的名称
//可以使用 @OneToMany 的 fetch 属性来修改默认的加载策略
//可以通过 @OneToMany 的 cascade 属性来修改默认的删除策略.
//注意: 若在 1 的一端的 @OneToMany 中使用 mappedBy 属性, 则 @OneToMany 端就不能再使用 @JoinColumn 属性了.
// @JoinColumn(name="CUSTOMER_ID")
@OneToMany(fetch=FetchType.LAZY,cascade={
CascadeType.REMOVE},mappedBy="customer")
public Set<Order> getOrders() {
return orders;
}
public void setOrders(Set<Order> orders) {
this.orders = orders;
}
}
插入数据 测试
//单向 1-n 关联关系执行保存时, 一定会多出 UPDATE 语句.
//因为 n 的一端在插入时不会同时插入外键列.
@Test
public void testOneToManyPersist(){
Customer customer = new Customer();
customer.setAge(18);
customer.setBirth(new Date());
customer.setCreatedTime(new Date());
customer.setEmail("[email protected]");
customer.setLastName("MM");
Order order1 = new Order();
order1.setOrderName("O-MM-1");
Order order2 = new Order();
order2.setOrderName("O-MM-2");
//建立关联关系
customer.getOrders().add(order1);
customer.getOrders().add(order2);
order1.setCustomer(customer);
order2.setCustomer(customer);
//执行保存操作
entityManager.persist(customer);
entityManager.persist(order1);
entityManager.persist(order2);
}
删除数据 测试
//默认情况下, 若删除 1 的一端, 则会先把关联的 n 的一端的外键置空 (变为null), 然后进行删除.
//可以通过 @OneToMany 的 cascade 属性来修改默认的删除策略.
//cascade={CascadeType.REMOVE} 表示在删除 1 的一端的时候 同步删除 多 的一端
@Test
public void testOneToManyRemove(){
Customer customer = entityManager.find(Customer.class, 8);
entityManager.remove(customer); //删除customer的时候 , order的外键会置空
}
修改数据 测试
@Test
public void testUpdate(){
Customer customer = entityManager.find(Customer.class, 10);
customer.getOrders().iterator().next().setOrderName("O-XXX-10");
}
双向一对多关系中,必须存在一个关系维护端,在 JPA 规范中,要求 many 的一方作为关系的维护端(owner side), one 的一方作为被维护端(inverse side)。
可以在 one 方指定 @OneToMany 注释并设置 mappedBy 属性,以指定它是这一关联中的被维护端,many 为维护端。
在 many 方指定 @ManyToOne 注释,并使用 @JoinColumn 指定外键名称
//1 的一方
private Set<Order> orders = new HashSet<>();
// @JoinColumn(name="CUSTOMER_ID")
@OneToMany(fetch=FetchType.LAZY,cascade={
CascadeType.REMOVE},mappedBy="customer")
public Set<Order> getOrders() {
return orders;
}
public void setOrders(Set<Order> orders) {
this.orders = orders;
}
//多 的一方
private Customer customer;
@JoinColumn(name="CUSTOMER_ID")
@ManyToOne(fetch=FetchType.LAZY)
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
//若是双向 1-n 的关联关系, 执行保存时
//若先保存 n 的一端, 再保存 1 的一端, 默认情况下, 会多出 n 条 UPDATE 语句.
//若先保存 1 的一端, 则会多出 n/2 条 UPDATE 语句
//建议 : 在进行双向 1-n 关联关系时, 建议使用 n 的一方来维护关联关系, 而 1 的一方不维护关联系, 这样会有效的减少 SQL 语句.
//注意: 若在 1 的一端的 @OneToMany 中使用 mappedBy 属性, 则 @OneToMany 端就不能再使用 @JoinColumn 属性了.
比如: 部门与经理 一个部门只有一个经理 一个经理只管一个部门
基于外键的 1-1 关联关系:在双向的一对一关联中,需要在关系被维护端(inverse side)中的 @OneToOne 注释中指定 mappedBy,以指定是这一关联中的被维护端。同时需要在关系维护端(owner side)建立外键列指向关系被维护端的主键列。
如果延迟加载要起作用, 就必须设置一个代理对象.
Manager 其实可以不关联一个 Department
如果有 Department 关联就设置为代理对象而延迟加载, 如果不存在关联的 Department 就设置 null, 因为外键字段是定义在 Department 表中的,Hibernate 在不读取 Department 表的情况是无法判断是否有关联有 Deparmtment, 因此无法判断设置 null 还是代理对象, 而统一设置为代理对象,也无法满足不关联的情况, 所以无法使用延迟加载,只 有显式读取 Department.
@Table(name="JPA_DEPARTMENTS")
@Entity
public class Department {
private Integer id;
private String deptName;
private Manager mgr;
//使用 @OneToOne 来映射 1-1 关联关系。
//若需要在当前数据表中添加外键则需要使用 @JoinColumn 来进行映射. 注意, 1-1 关联关系, 所以需要添加 unique=true
@JoinColumn(name="MGR_ID", unique=true)
@OneToOne(fetch=FetchType.LAZY)
public Manager getMgr() {
return mgr;
}
public void setMgr(Manager mgr) {
this.mgr = mgr;
}
}
@Table(name="JPA_MANAGERS")
@Entity
public class Manager {
private Integer id;
private String mgrName;
private Department dept;
//对于不维护关联关系, 没有外键的一方, 使用 @OneToOne 来进行映射, 建议设置 mappedBy=true
@OneToOne(mappedBy="mgr")
public Department getDept() {
return dept;
}
public void setDept(Department dept) {
this.dept = dept;
}
}
添加数据 测试
//双向 1-1 的关联关系, 建议先保存不维护关联关系的一方, 即没有外键的一方, 这样不会多出 UPDATE 语句.
// 如果先保存 有 外键的一方 会多出 UPDATE 语句
@Test
public void testOneToOnePersistence(){
Manager mgr = new Manager();
mgr.setMgrName("M-BB");
Department dept = new Department();
dept.setDeptName("D-BB");
//设置关联关系
mgr.setDept(dept);
dept.setMgr(mgr);
//执行保存操作
entityManager.persist(mgr);
entityManager.persist(dept);
}
获取数据 测试
//1.默认情况下, 若获取维护关联关系的一方(有外键的一方), 则会通过左外连接获取其关联的对象.
//但可以通过 @OntToOne 的 fetch 属性来修改加载策略.
@Test
public void testOneToOneFind(){
Department dept = entityManager.find(Department.class, 1);
System.out.println(dept.getDeptName());
System.out.println(dept.getMgr().getClass().getName());
}
在双向多对多关系中,我们必须指定一个关系维护端(owner side),可以通过 @ManyToMany 注释中指定 mappedBy 属性来标识其为关系维护端。
双向多对多 必须 有一方 放弃维护关联关系 , 否则就会出现主键重复的问题
比如: 商品与类别 , 一个类别有多个商品 一个商品可以属于多个类别
商品类: Item 类别类: Category
类别
@Table(name="JPA_CATEGORIES")
@Entity
public class Category {
private Integer id;
private String categoryName;
private Set<Item> items = new HashSet<>(); //商品可以有多个类别
@ManyToMany(mappedBy="categories")
public Set<Item> getItems() {
return items;
}
public void setItems(Set<Item> items) {
this.items = items;
}
}
商品
@Table(name="JPA_ITEMS")
@Entity
public class Item {
private Integer id;
private String itemName;
private Set<Category> categories = new HashSet<>(); //类别可以有多个商品
//使用 @ManyToMany 注解来映射多对多关联关系
//使用 @JoinTable 来映射中间表
//1. name 指向中间表的名字
//2. joinColumns 映射当前类所在的表在中间表中的外键
//2.1 name 指定外键列的列名
//2.2 referencedColumnName 指定外键列关联当前表的哪一列
//3. inverseJoinColumns 映射关联的类所在中间表的外键
@JoinTable(name="ITEM_CATEGORY",
joinColumns={
@JoinColumn(name="ITEM_ID", referencedColumnName="ID")},
inverseJoinColumns={
@JoinColumn(name="CATEGORY_ID", referencedColumnName="ID")})
@ManyToMany
public Set<Category> getCategories() {
return categories;
}
public void setCategories(Set<Category> categories) {
this.categories = categories;
}
}
保存数据 测试
//多对所的保存
@Test
public void testManyToManyPersist(){
Item i1 = new Item();
i1.setItemName("i-1");
Item i2 = new Item();
i2.setItemName("i-2");
Category c1 = new Category();
c1.setCategoryName("C-1");
Category c2 = new Category();
c2.setCategoryName("C-2");
//设置关联关系
i1.getCategories().add(c1);
i1.getCategories().add(c2);
i2.getCategories().add(c1);
i2.getCategories().add(c2);
c1.getItems().add(i1);
c1.getItems().add(i2);
c2.getItems().add(i1);
c2.getItems().add(i2);
//执行保存 8条 insert 语句
entityManager.persist(i1);
entityManager.persist(i2);
entityManager.persist(c1);
entityManager.persist(c2);
}
获取数据 测试
//对于关联的集合对象, 默认使用懒加载的策略.
//使用维护关联关系的一方获取, 还是使用不维护关联关系的一方获取, SQL 语句相同.
//不管使用有外键的一方 还是 使用没有外键的一方 最后 执行的 sql 语句是相同的
@Test
public void testManyToManyFind(){
// Item item = entityManager.find(Item.class, 5);
// System.out.println(item.getItemName());
//
// System.out.println(item.getCategories().size());
Category category = entityManager.find(Category.class, 3);
System.out.println(category.getCategoryName());
System.out.println(category.getItems().size());
}
二级缓存的作用是什么
二级缓存的目的: 可以跨 entityManager , 关闭与开启都不会影响 耳机缓存的数据 , 就只用发 一条 sql 语句
shared-cache-mode节点:若 JPA 实现支持二级缓存,该节点可以配置在当前的持久化单元中是否启用二级缓存,可配置如下值:
//只会执行一条 sql 使用了一级缓存 , 缓存中有customer1 执行过的记录
@Test
public void testSecondLevelCache(){
Customer customer1 = entityManager.find(Customer.class, 1);
Customer customer2 = entityManager.find(Customer.class, 1);
}
// 会执行两条 sql , 因为 entityManager 关闭 再 开启 , 一级缓存中的数据就被清除了
@Test
public void testSecondLevelCache(){
Customer customer1 = entityManager.find(Customer.class, 1);
transaction.commit();
entityManager.close();
entityManager = entityManagerFactory.createEntityManager();
transaction = entityManager.getTransaction();
transaction.begin();
Customer customer2 = entityManager.find(Customer.class, 1);
}
配置二级缓存
在persistence.xml中添加以下代码 ,此外还需要导入 依赖包 ehcache
<class>com.atguigu.jpa.helloworld.Customerclass>
<class>com.atguigu.jpa.helloworld.Orderclass>
<shared-cache-mode>ENABLE_SELECTIVEshared-cache-mode>
<properties>
<property name="hibernate.cache.use_second_level_cache" value="true"/>
<property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.EhCacheRegionFactory"/>
<property name="hibernate.cache.use_query_cache" value="true"/>
properties>
然后在要缓存的类上加上注解: @Cacheable(true) 表示这个类开启二级缓存
@Cacheable(true)
//数据库中要创建的对应的表名
@Table(name="JPA_CUTOMERS")
//告诉JPA这是一个需要持久化的类
@Entity
public class Customer {
}
使用范围
NamedQuery
在类上加注解NamedQuery
@NamedQuery(name="testNamedQuery", query="FROM Customer c WHERE c.id = ?")
@Cacheable(true)
//数据库中要创建的对应的表名
@Table(name="JPA_CUTOMERS")
//告诉JPA这是一个需要持久化的类
@Entity
public class Customer {
NamedQuery测试
//createNamedQuery 适用于在实体类前使用 @NamedQuery 标记的查询语句
@Test
public void testNamedQuery(){
Query query = entityManager.createNamedQuery("testNamedQuery").setParameter(1, 3);
Customer customer = (Customer) query.getSingleResult();
System.out.println(customer);
}
createNativeQuery 测试
//createNativeQuery 适用于本地 SQL
@Test
public void testNativeQuery(){
String sql = "SELECT age FROM jpa_cutomers WHERE id = ?";
Query query = entityManager.createNativeQuery(sql).setParameter(1, 3);
Object result = query.getSingleResult();
System.out.println(result);
}
select语句用于执行查询。其语法可表示为:
select_clause
form_clause
[where_clause]
[groupby_clause]
[having_clause]
[orderby_clause]
select-from 子句
查询所有实体
调用 EntityManager 的 createQuery() 方法可创建查询对象,接着调用 Query 接口的 getResultList() 方法就可获得查询结果集。例如:
Query query = entityManager.createQuery( "select o from Order o");
List orders = query.getResultList();
Iterator iterator = orders.iterator();
while( iterator.hasNext() ) {
// 处理Order
}
where子句
获取对象集合
@Test
public void testHelloJPQL(){
String jpql = "FROM Customer c WHERE c.age > ?"; //要执行的语句
Query query = entityManager.createQuery(jpql); //创建createQuery 接口对象
//占位符的索引是从 1 开始
query.setParameter(1, 1); //为查询语句的指定位置参数赋值。
List<Customer> customers = query.getResultList(); //执行select语句并返回结果集
System.out.println(customers.size());
}
只查询部分属性
//默认情况下, 若只查询部分属性, 则将返回 Object[] 类型的结果. 或者 Object[] 类型的 List.
//如果希望返回的是对象 ,也可以在实体类中创建对应的构造器, 然后再 JPQL 语句中利用对应的构造器返回实体类的对象.
@Test
public void testPartlyProperties(){
//返回数组 , 数组中是 lastName 与 age 连个元素
//String jpql = "SELECT c.lastName, c.age FROM Customer c WHERE c.id > ?";
String jpql = "SELECT new Customer(c.lastName, c.age) FROM Customer c WHERE c.id > ?";
List result = entityManager.createQuery(jpql).setParameter(1, 1).getResultList();
System.out.println(result);
}
前提 必须在 persistence.xml 配置文件中开启二级缓存
//使用 hibernate 的查询缓存.
@Test
public void testQueryCache(){
String jpql = "FROM Customer c WHERE c.age > ?";
//Query query = entityManager.createQuery(jpql); 最初的写法 , 默认是两条sql语句
// 在后天添加 setHint(QueryHints.HINT_CACHEABLE, true) 表示开启缓存 , 就只会执行一条sql
Query query = entityManager.createQuery(jpql).setHint(QueryHints.HINT_CACHEABLE, true);
//占位符的索引是从 1 开始
query.setParameter(1, 1);
List<Customer> customers = query.getResultList();
System.out.println(customers.size());
//query = entityManager.createQuery(jpql); 最初的写法 , 默认是两条sql语句
query = entityManager.createQuery(jpql).setHint(QueryHints.HINT_CACHEABLE, true);
//占位符的索引是从 1 开始
query.setParameter(1, 1);
customers = query.getResultList();
System.out.println(customers.size());
}
order by子句用于对查询结果集进行排序。和SQL的用法类似,可以用 “asc“ 和 "desc“ 指定升降序。如果不显式注明,默认为升序。
select o from Orders o order by o.id
select o from Orders o order by o.address.streetNumber desc
select o from Orders o order by o.customer asc, o.id desc
@Test
public void testOrderBy(){
String jpql = "FROM Customer c WHERE c.age > ? ORDER BY c.age DESC";
Query query = entityManager.createQuery(jpql).setHint(QueryHints.HINT_CACHEABLE, true);
//占位符的索引是从 1 开始
query.setParameter(1, 1);
List<Customer> customers = query.getResultList();
System.out.println(customers.size());
}
having子句
//查询 order 数量大于 2 的那些 Customer
@Test
public void testGroupBy(){
String jpql = "SELECT o.customer FROM Order o "
+ "GROUP BY o.customer "
+ "HAVING count(o.id) >= 2";
List<Customer> customers = entityManager.createQuery(jpql).getResultList();
System.out.println(customers);
}
在JPQL中,很多时候都是通过在实体类中配置实体关联的类属性来实现隐含的关联(join)查询。例如:
select o from Orders o where o.address.streetNumber=2000
上述JPQL语句编译成以下SQL时就会自动包含关联,默认为左关联。
在某些情况下可能仍然需要对关联做精确的控制。为此,JPQL 也支持和 SQL 中类似的关联语法。如:
left out join / left join
inner join
left join / inner join fetch
其中,left join和left out join等义,都是允许符合条件的右边表达式中的实体为空。
例如,
/**
* JPQL 的关联查询同 HQL 的关联查询.
*/
@Test
public void testLeftOuterJoinFetch(){
//因为customer与Orders关联了, 并且 下面又使用Orders , 所以不使用关联查询就会发送两条sql
// String jpql = "FROM Customer c WHERE c.id = ?";
//建议加上 FETCH 这样得到的就是一个对象 , 并且对象已经初始化成功了
String jpql = "FROM Customer c LEFT OUTER JOIN FETCH c.orders WHERE c.id = ?";
Customer customer =
(Customer) entityManager.createQuery(jpql).setParameter(1, 12).getSingleResult();
System.out.println(customer.getLastName());
System.out.println(customer.getOrders().size());
//如果不使用 FETCH 返回的可能是多条记录 ,每个记录都是一个对象数组 , 而使用getSingleResult接受单个对象 就会报错
// List
// System.out.println(result);
}
JPQL也支持子查询,在 where 或 having 子句中可以包含另一个查询。当子查询返回多于 1 个结果集时,它常出现在 any、all、exist s表达式中用于集合匹配查询。它们的用法与SQL语句基本相同。
@Test
public void testSubQuery(){
//查询所有 Customer 的 lastName 为 YY 的 Order
String jpql = "SELECT o FROM Order o "
+ "WHERE o.customer = (SELECT c FROM Customer c WHERE c.lastName = ?)";
Query query = entityManager.createQuery(jpql).setParameter(1, "YY");
List<Order> orders = query.getResultList();
System.out.println(orders.size());
}
JPQL提供了以下一些内建函数,包括字符串处理函数、算术函数和日期函数。
字符串处理函数主要有:
算术函数主要有 abs、mod、sqrt、size 等。Size 用于求集合的元素个数。
日期函数主要为三个,即 current_date、current_time、current_timestamp,它们不需要参数,返回服务器上的当前日期、时间和时戳。
//使用 jpql 内建的函数
@Test
public void testJpqlFunction(){
String jpql = "SELECT lower(c.email) FROM Customer c";
List<String> emails = entityManager.createQuery(jpql).getResultList();
System.out.println(emails);
}
//可以使用 JPQL 完成 UPDATE 和 DELETE 操作.
@Test
public void testExecuteUpdate(){
String jpql = "UPDATE Customer c SET c.lastName = ? WHERE c.id = ?";
Query query = entityManager.createQuery(jpql).setParameter(1, "YYY").setParameter(2, 12);
query.executeUpdate();
}
主要从两方面
三种整合方式:
SpringBoot整合JPA