JPA
jdbc技术:
JPA是什么
Java Persistence Api:用于对象持久化的api
Java EE 5.0平台标准的ORM框架,使得应用程序以统一的方式访问持久层
JPA和hibernate的关系
JPA是hibernate的一个抽象(就像JDBC和JDBC驱动一样)
JPA是规范:JPA本质上是一种ORM规范,不是ORM框架(因为JPA未提供ORM实现,它只是提供一些规范,具体实现有ORM厂商提供)
hibernate是实现:hibernate除了是ORM框架之外,还是一种JPA实行
从功能上来说,JPA是hibernate的一个功能子集
JPA的提供商
JPA的目标之一是制定一个由很多供应商的e实现的API,hibernate、OpenJPA、TopLink
JPA的优势
标准化:提供相同的API,这保证了基于JPA开发的企业应用能够经过少量的修改就能够在不同的JPA实现框架下进行
简单易用、集成方便:JPA的主要目标之一就是提供更加简单的编程模型,在JPA框架下创建实体和创建Java类一样简单,只需使用注解即可
执行速度可媲美JDBC的查询能力:JPA的查询语言是面向对象的,支持批量修改,join,group by、having等通常只有SQL才提供的高级查询语言,甚至还支持子查询
支持面向对象的高级查询:JPA中能够支持面向对象的高级特性,如类之间的继承,多态和类之间的复杂关系,最大限度的支持面向对象
JPA包括三方面的内容:
ORM映射元数据:JPA支持XML和JDK 5.0注解两种数据形式,元数据描述对象和表之间的关系,框架据此将实体对象持久化到数据库中
JPA的API:用来操作实体对象,执行CURD对象,框架在后台完成的所有事情,开发者从繁琐的JDBC和SQL中解放出来
查询语言(JPQL):通过面向对象而非面向数据库的查询语言,避免程序和具体的SQL紧密耦合
使用JPA持久化对象的步骤
1、创建persistence.xml文件,在这个配置文件配置持久化操作
指定跟哪个数据库进行交互
需要指定JPA使用哪个框架以及配置该框架的基本属性
2、创建实体类
使用注解来描述实体类和数据库之间的关系
3、使用JPA API完成数据的增删查改
入门程序:
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<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" transaction-type="RESOURCE_LOCAL">
<!--
配置使用什么 ORM 产品来作为 JPA 的实现
1. 实际上配置的是 javax.persistence.spi.PersistenceProvider 接口的实现类
2. 若 JPA 项目中只有一个 JPA 的实现产品, 则也可以不配置该节点.
-->
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<!-- 添加持久化类 -->
<class>entity.Customer</class>
<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="root"/>
<!-- 配置 JPA 实现产品的基本属性. 配置 hibernate 的基本属性 -->
<property name="hibernate.format_sql" value="true"/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
</properties>
</persistence-unit>
</persistence>
JPA的基本注解:
@Entity
指出该Java为实体类,将其映射到指定的数据库表
@Table
用于当实体类名和数据库表名不一样,一般放在@Entity之前
name属性:指定在数据库中的表名
@Id
用于指定该属性是数据库的主键列,可以用于属性,也可用于getter方法之前
@GeneratedValue
主键的生成策略
@Basic
表示该属性需要映射成为数据库中的列,默认每个getter方法都有
fetch表示该属性的读取策略,有EAGER、LAZY两种
optional属性表示该属性是否为null,默认为true
@Column(name="LAST_NAME",length=50,nullable=false)
当数据库列和属性不一致时候使用
@Transient
表示该属性不映射到数据库列
@Temporal(TemporalType.DATE)
对日期精度的处理
用表来生成主键
将当前主键的值保存到一个数据库表中,主键的值每次都是从指定的表中查询获取
这种方法的生成主键的策略使用于任何数据库,不会造成数据库的不兼容问题
@Id
@GeneratedValue(strategy=GenerationType.TABLE, generator="id_generate")
@TableGenerator(name="id_generate",
table="id_generator",
pkColumnName="pk_name",
pkColumnValue="customer_id",
valueColumnName="pk_value",
allocationSize=10)
private Integer id;
Persistence:用于获取EntityManagerFactory实例
//1、创建EntityManagerFactory
EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory("jpa");
EntityManagerFactory用来创建EntityManager
//2、创建EntityManager
EntityManager entityManager = entityManagerFactory.createEntityManager();
EntityManager:
查询方法:
@Test
public void getReference(){
//类似于 hibernate 中 Session 的 load 方法(懒加载)
Customer customer = entityManager.getReference(Customer.class, 10);
System.out.println(customer.getClass().getName());//entity.Customer_$$_javassist_0
System.out.println(customer);
}
@Test
public void find() {
//类似于 hibernate 中 Session 的 get 方法. (立即加载)
Customer customer = entityManager.find(Customer.class, 10);
System.out.println(customer);
}
持久化操作:
@Test
public void persistence() {
//类似于 hibernate 的 save 方法. 使对象由临时状态变为持久化状态.
//和 hibernate 的 save 方法的不同之处: 若对象有 id, 则不能执行 insert 操作, 而会抛出异常.
Customer customer = new Customer(null, "Jacky", "jacky@qq.com", 35);
entityManager.persist(customer);
System.out.println(customer);
}
删除:
@Test
public void remove() {
//类似于 hibernate 中 Session 的 delete 方法. 把对象对应的记录从数据库中移除
//但注意: 该方法只能移除 持久化 对象. 而 hibernate 的 delete 方法实际上还可以移除 游离对象.
Customer customer = entityManager.find(Customer.class, 50);
entityManager.remove(customer);
}
merge方法:
@Test
public void merge4() {
Customer customer = new Customer(70, "Bob_update", "bobupdate@qq.com", 105);
Customer customer2 = entityManager.find(Customer.class, 70);
//若传入的是一个游离对象, 即传入的对象有 OID.
//1. 若在 EntityManager 缓存中有对应的对象
//2. JPA 会把游离对象的属性复制到查询到EntityManager 缓存中的对象中.
//3. EntityManager 缓存中的对象执行 UPDATE.
Customer merge = entityManager.merge(customer);
System.out.println(customer == customer2);//false
}
@Test
public void merge3() {
Customer customer = new Customer(70, "Bob_update", "bob@qq.com", 105);
//若传入的是一个游离对象, 即传入的对象有 OID.
//1. 若在 EntityManager 缓存中没有该对象
//2. 若在数据库中也有对应的记录
//3. JPA 会查询对应的记录, 然后返回该记录对一个的对象, 再然后会把游离对象的属性复制到查询到的对象中.
//4. 对查询到的对象执行 update 操作.
Customer merge = entityManager.merge(customer);
System.out.println(customer==merge);//false
}
@Test
public void merge2() {
Customer customer = new Customer(70, "Bob", "bob@qq.com", 15);
//若传入的是一个游离对象, 即传入的对象有 OID.
//1. 若在 EntityManager 缓存中没有该对象
//2. 若在数据库中也没有对应的记录
//3. JPA 会创建一个新的对象, 然后把当前游离对象的属性复制到新创建的对象中
//4. 对新创建的对象执行 insert 操作.
Customer merge = entityManager.merge(customer);
System.out.println(customer.getId());
System.out.println(merge.getId());
}
@Test
public void merge1() {
//1. 若传入的是一个临时对象
//会创建一个新的对象, 把临时对象的属性复制到新的对象中, 然后对新的对象执行持久化操作. 所以
//新的对象中有 id, 但以前的临时对象中没有 id.
Customer customer = new Customer(null, "Marry", "marry@qq.com", 15);
Customer merge = entityManager.merge(customer);
System.out.println(customer.getId());//null
System.out.println(merge.getId());
}
flush()同步持久上下文环境,即将持久上下文环境的所有未保存实体的状态信息保存到数据库
@Test
public void refresh(){
Customer customer = entityManager.find(Customer.class, 10);
customer = entityManager.find(Customer.class, 10);
//同 hibernate 中 Session 的 refresh 方法.
entityManager.refresh(customer);
}
@Test
public void flush() {
Customer customer = entityManager.find(Customer.class, 10);
System.out.println(customer);
customer.setEmail(customer.getEmail()+"update");
//同 hibernate 中 Session 的 flush 方法.
entityManager.flush();
System.out.println(customer);
}
映射单向多对一的关联关系
@Test
public void manyToOne() {
Order order1 = new Order();
order1.setOrderName("O-BB-1");
Order order2 = new Order();
order2.setOrderName("O-BB-2");
Customer customer = new Customer(null, "Bob", "bob@qq.com", 24);
order1.setCustomer(customer);
order2.setCustomer(customer);
//保存多对一时, 建议先保存 1 的一端, 后保存 n 的一端, 这样不会多出额外的 UPDATE 语句.
entityManager.persist(customer);//此时3个insert语句
entityManager.persist(order1);
entityManager.persist(order2);
entityManager.persist(customer);//此时3个insert语句,2个update语句
}
查询:
@ManyToOne(fetch=FetchType.LAZY)
@Test
public void find() {
//默认情况下, 使用左外连接的方式来获取 n 的一端的对象和其关联的 1 的一端的对象.
//可使用 @ManyToOne 的 fetch 属性来修改默认的关联属性的加载策略
Order order = entityManager.find(Order.class, 1);//默认情况,发送所外连接的语句一条语句
System.out.println(order.getOrderName());
System.out.println(order.getCustomer().getLastName());
}
单向一对多:
保存:
@Test
public void save() {
Customer customer = new Customer();
customer.setAge(18);
customer.setEmail("AA@163.com");
customer.setLastName("AA");
Order order1 = new Order();
order1.setOrderName("O-AA-1");
Order order2 = new Order();
order2.setOrderName("O-AA-2");
//建立关联关系
customer.getOrders().add(order1);
customer.getOrders().add(order2);
//单向 1-n 关联关系执行保存时, 一定会多出 UPDATE 语句.
//因为 n 的一端在插入时不会同时插入外键列.
//执行保存操作
entityManager.persist(customer); //3个insert语句,2个update语句,一方必须维护关系
entityManager.persist(order1);
entityManager.persist(order2);
}
查找:
@Test
public void find() {
Customer customer = entityManager.find(Customer.class, 1);
System.out.println(customer.getLastName());
//默认对关联的多的一方使用懒加载的加载策略.
//可以使用 @OneToMany 的 fetch 属性来修改默认的加载策略
System.out.println(customer.getOrders().size());
}
删除:
@Test
public void remove(){
//默认情况下, 若删除 1 的一端, 则会先把关联的 n 的一端的外键置空, 然后进行删除.
//可以通过 @OneToMany 的 cascade 属性来修改默认的删除策略.
Customer customer = entityManager.find(Customer.class, 1);
entityManager.remove(customer);
}
双向一对多:
新增:
@Test
public void manyToOne() {
Order order1 = new Order();
order1.setOrderName("O-CC-1");
Order order2 = new Order();
order2.setOrderName("O-CC-2");
Customer customer = new Customer(null, "CC", "CC@qq.com", 24);
order1.setCustomer(customer);
order2.setCustomer(customer);
customer.getOrders().add(order1);
customer.getOrders().add(order2);
//在进行双向 1-n 关联关系时, 建议使用 n 的一方来维护关联关系, 而 1 的一方不维护关联系, 这样会有效的减少 SQL 语句.
//注意: 若在 1 的一端的 @OneToMany 中使用 mappedBy 属性, 则 @OneToMany 端就不能再使用 @JoinColumn 属性了.
entityManager.persist(customer);//此时3个insert语句,2个update语句
entityManager.persist(order1);
entityManager.persist(order2);
//entityManager.persist(customer);//此时3个insert语句,4个update语句
}
双向一对一:
@Test
public void save() {
Manager mgr = new Manager();
mgr.setMgrName("M-AA");
Dept dept = new Dept();
dept.setDeptName("D-AA");
//设置关联关系
mgr.setDept(dept);
dept.setManager(mgr);
//执行保存操作
//双向 1-1 的关联关系, 建议先保存不维护关联关系的一方, 即没有外键的一方, 这样不会多出 UPDATE 语句.
entityManager.persist(mgr);
entityManager.persist(dept);
}
@Test
public void find(){
//1.默认情况下, 若获取维护关联关系的一方, 则会通过左外连接获取其关联的对象.
//但可以通过 @OntToOne 的 fetch 属性来修改加载策略.
Dept dept = entityManager.find(Dept.class, 1);
System.out.println(dept.getDeptName());
//1. 默认情况下, 若获取不维护关联关系的一方, 则也会通过左外连接获取其关联的对象.
//可以通过 @OneToOne 的 fetch 属性来修改加载策略. 但依然会再发送 SQL 语句来初始化其关联的对象
//这说明在不维护关联关系的一方, 不建议修改 fetch 属性.
System.out.println(dept.getManager().getClass().getName());
}
双向多对多:
@ManyToMany(mappedBy="categorys")
private Set<Item> items = new HashSet<>();
//使用 @ManyToMany 注解来映射多对多关联关系
//使用 @JoinTable 来映射中间表
//1. name 指向中间表的名字
//2. joinColumns 映射当前类所在的表在中间表中的外键
//2.1 name 指定外键列的列名
//2.2 referencedColumnName 指定外键列关联当前表的哪一列
//3. inverseJoinColumns 映射关联的类所在中间表的外键
@ManyToMany
@JoinTable(name = "item_category", joinColumns = {@JoinColumn(name = "item_id",
referencedColumnName = "id")},
inverseJoinColumns = {@JoinColumn(name = "categoty_id",
referencedColumnName = "id")})
private Set<Category> categorys = new HashSet<>();
二级缓存:加入包、配置文件
@Entity
@Table(name = "jpa_customer")
@Cacheable(true)
public class Customer implements Serializable
<!--
配置二级缓存的策略
ALL:所有的实体类都被缓存
NONE:所有的实体类都不被缓存.
ENABLE_SELECTIVE:标识 @Cacheable(true) 注解的实体类将被缓存
DISABLE_SELECTIVE:缓存除标识 @Cacheable(false) 以外的所有实体类
UNSPECIFIED:默认值,JPA 产品默认值将被使用
-->
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
<!-- 二级缓存相关 -->
<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"/>
@Test
public void secondLevelCache(){
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);
}
JPQL语言
@Test
public void jpql() {
String jpql = "FROM Customer c WHERE c.age > ?";
Query query = entityManager.createQuery(jpql);
// 占位符的索引是从 1 开始
query.setParameter(1, 20);
List<Customer> customers = query.getResultList();
System.out.println(customers);// [Customer [id=1, lastName=CC, email=CC@qq.com, age=24], Customer [id=3,
}
@Test
public void partlyProperties() {
// 默认情况下, 若只查询部分属性, 则将返回 Object[] 类型的结果. 或者 Object[] 类型的 List.
// 也可以在实体类中创建对应的构造器, 然后再 JPQL 语句中利用对应的构造器返回实体类的对象.
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);// [Customer [id=null, lastName=AA, email=null, age=13], Customer [id=null,
// lastName=dd, email=null, age=23]]
}
@NamedQuery(name="testNamedQuery", query="FROM Customer c WHERE c.id = ?")
@Entity
@Table(name = "jpa_customer")
@Cacheable(true)
public class Customer implements Serializable
@Test
public void namedQuery(){
//createNamedQuery 适用于在实体类前使用 @NamedQuery 标记的查询语句
Query query = entityManager.createNamedQuery("testNamedQuery").setParameter(1, 3);
Customer customer = (Customer) query.getSingleResult();
System.out.println(customer);//Customer [id=3, lastName=dd, email=dd, age=23]
}
@Test
public void nativeQuery(){
//createNativeQuery 适用于本地 SQL
String sql = "SELECT age FROM jpa_customer WHERE id = ?";
Query query = entityManager.createNativeQuery(sql).setParameter(1, 3);
Object result = query.getSingleResult();
System.out.println(result);
}
查询缓存:
@Test
public void queryCache(){
//使用 hibernate 的查询缓存.
String jpql = "FROM Customer c WHERE c.age > ?";
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).setHint(QueryHints.HINT_CACHEABLE, true);
//占位符的索引是从 1 开始
query.setParameter(1, 1);
customers = query.getResultList();
System.out.println(customers.size());
}
order by:
@Test
public void orderBy(){
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());
}
group by :
@Test
public void groupBy(){
//查询 order 数量大于 2 的那些 Customer
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);
}
关联查询:
@Test
public void leftOuterJoinFetch(){
String jpql = "FROM Customer c LEFT OUTER JOIN FETCH c.orders WHERE c.id = ?";
//JPQL 的关联查询同 HQL 的关联查询
Customer customer =
(Customer) entityManager.createQuery(jpql).setParameter(1, 1).getSingleResult();
//System.out.println(customer.getLastName());
//System.out.println(customer.getOrders().size());
List<Object[]> result = entityManager.createQuery(jpql).setParameter(1, 1).getResultList();
System.out.println(result);
}
子查询:
@Test
public void subQuery(){
//查询所有 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, "AA");
List<Order> orders = query.getResultList();
System.out.println(orders.size());
}
内置函数:
@Test
public void jpqlFunction(){
String jpql = "SELECT upper(c.email) FROM Customer c";
List<String> emails = entityManager.createQuery(jpql).getResultList();
System.out.println(emails);
}
spring和jpa的整合:
<!-- 配置自动扫描的包 -->
<context:component-scan base-package="jpa"></context:component-scan>
<!-- 配置 C3P0 数据源 -->
<context:property-placeholder location="classpath:db.properties"/>
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<!-- 配置其他属性 -->
</bean>
<!-- 配置 EntityManagerFactory -->
<bean id="entityManagerFactory"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<!-- 配置 JPA 提供商的适配器. 可以通过内部 bean 的方式来配置 -->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"></bean>
</property>
<!-- 配置实体类所在的包 -->
<property name="packagesToScan" value="jpa.spring.entities"></property>
<!-- 配置 JPA 的基本属性. 例如 JPA 实现产品的属性 -->
<property name="jpaProperties">
<props>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.hbm2ddl.auto">update</prop>
</props>
</property>
</bean>
<!-- 配置 JPA 使用的事务管理器 -->
<bean id="transactionManager"
class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"></property>
</bean>
<!-- 配置支持基于注解是事务配置 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
@Repository
public class PersonDao {
@PersistenceContext
private EntityManager entityManager;
public void save(Person person) {
entityManager.persist(person);
}
}