JPA的使用
一、maven工程搭建
基于IDEA开发工具
1). maven坐标 pom.xml
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.hibernate.version>5.0.7.Final</project.hibernate.version>
</properties>
<dependencies>
<!-- junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- hibernate对jpa的支持包 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${project.hibernate.version}</version>
</dependency>
<!-- c3p0 -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-c3p0</artifactId>
<version>${project.hibernate.version}</version>
</dependency>
<!-- log日志 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- Mysql and MariaDB -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
</dependencies>
2). resources/META-INF/persistence.xml
jpa规定的文件目录和文件名!
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
<!--
需要配置persistence-unit节点
持久化单元:
name:持久化单元名称
transaction-type:事务管理的方式
JTA:分布式事务管理
RESOURCE_LOCAL:本地事务管理
-->
<persistence-unit name="myJpa" transaction-type="RESOURCE_LOCAL">
<!--jpa的实现方式 -->
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<!--可选配置:配置jpa实现方的配置信息-->
<properties>
<!--
数据库信息
用户名,javax.persistence.jdbc.user
密码, javax.persistence.jdbc.password
驱动, javax.persistence.jdbc.driver
数据库地址 javax.persistence.jdbc.url
-->
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="javax.persistence.jdbc.password" value="abc123"/>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql:///jpa"/>
<!--
配置jpa实现方(hibernate)的配置信息
hibernate.show_sql是否在控制台显示sql :
false
true
hibernate.hbm2ddl.auto自动创建数据库表
create : 程序运行时创建数据库表(了解,如果有表,先删除表再创建)
update : 程序运行时创建表(了解,如果有表,不会创建表)
none : 不会创建表【实际开发中使用】
-->
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.hbm2ddl.auto" value="none" />
</properties>
</persistence-unit>
</persistence>
3). 数据库准备
CREATE DATABASE IF NOT EXISTS jpa DEFAULT CHARACTER SET utf8;
USE jpa;
/*创建客户表*/
CREATE TABLE cst_customer (
cust_id BIGINT(32) NOT NULL AUTO_INCREMENT COMMENT '客户编号(主键)',
cust_name VARCHAR(32) NOT NULL COMMENT '客户名称(公司名称)',
cust_source VARCHAR(32) DEFAULT NULL COMMENT '客户信息来源',
cust_industry VARCHAR(32) DEFAULT NULL COMMENT '客户所属行业',
cust_level VARCHAR(32) DEFAULT NULL COMMENT '客户级别',
cust_address VARCHAR(128) DEFAULT NULL COMMENT '客户联系地址',
cust_phone VARCHAR(64) DEFAULT NULL COMMENT '客户联系电话',
PRIMARY KEY (`cust_id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
二、实体类关系映射配置详解 Customer
所有的注解都是使用JPA的规范提供的注解,在导入注解包的时候,一定要导入javax.persistence下的
package com.lingting.pojo;
import javax.persistence.*;
/**
*
* 实体类,配置映射关系
* 1.实体类和表的映射关系注解
* @Entity: 声明当前类是实体类
* @Table : 配置实体类和表的映射关系
* name : 配置数据库表的名称
* 2.实体类中属性和表中字段的映射关系
*/
@Entity
@Table(name = "cst_customer")
public class Customer {
/** @GeneratedValue:配置主键的生成策略,strategy 值如下:
*
* GenerationType.IDENTITY :自增,mysql
* 底层数据库必须支持自动增长(底层数据库支持的自动增长方式,对id自增):注解用法
* @Id
* @GeneratedValue(strategy = GenerationType.IDENTITY)
* @Column(name = "cust_id")
* ...
*
* GenerationType.SEQUENCE : 序列,oracle
* 底层数据库必须支持序列:用法
* @Id
* @GeneratedValue(strategy = GenerationType.SEQUENCE,generator="payablemoney_seq")
* @SequenceGenerator(name="payablemoney_seq", sequenceName="seq_payment")
* ...
*
* SequenceGenerator注解字段源码含义
* String name();
* 表示该表主键生成策略的名称,它被引用在@GeneratedValue中设置的“generator”值中
* String sequenceName() default "";
* 属性表示生成策略用到的数据库序列名称。
* int initialValue() default 0;
* 表示主键初识值,默认为0
* int allocationSize() default 50;
* 表示每次主键值增加的大小,例如设置1,则表示每次插入新记录后自动加1,默认为50
*
* GenerationType.AUTO :
* 由程序自动的帮助我们选择主键生成策略: 用法
* @Id
* @GeneratedValue(strategy = GenerationType.AUTO)
* ...
*
* GenerationType.TABLE :
* jpa提供的一种机制,通过一张数据库表的形式帮助我们完成主键自增,用法
* @Id
* @GeneratedValue(strategy = GenerationType.TABLE, generator="pk_gen")
* @TableGenerator(name = "pk_gen",
* table="tb_generator", // 表名
* pkColumnName="gen_name", // 主键生成策略所对应键值 字段名
* pkColumnValue="PAYABLEMOENY_PK", // 主键的值
* valueColumnName="gen_value", // 主键生成的值,会累加
* allocationSize=1 // 主键生成的值累加的步长
* )
* ...
*
* TableGenerator注解字段源码含义
* String name();
* 表示该表主键生成策略的名称,它被引用在@GeneratedValue中设置的“generator”值中
* String table() default "";
* 表示表生成策略所持久化的表名,例如,这里表使用的是数据库中的“tb_generator”。
* String catalog() default "";
* String schema() default "";
* catalog和schema具体指定表所在的目录名或是数据库名
* String pkColumnName() default "";
* 属性的值表示在持久化表中,该主键生成策略所对应键值的名称。
* 例如在“tb_generator”中将“gen_name”作为主键的键值
* String pkColumnValue() default "";
* 属性的值表示在持久化表中,该生成策略所对应的主键。
* 例如在“tb_generator”表中,将“gen_name”的值为“CUSTOMER_PK”。
* String valueColumnName() default "";
* 属性的值表示在持久化表中,该主键当前所生成的值,它的值将会随着每次创建累加。
* 例如,在“tb_generator”中将“gen_value”作为主键的值
* int initialValue() default 0;
* 表示主键初识值,默认为0。
* int allocationSize() default 50;
* 表示每次主键值增加的大小,例如设置成1,则表示每次创建新记录后自动加1,默认为50。
* UniqueConstraint[] uniqueConstraints() default {};
*/
@Id // 声明主键的配置
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "cust_id") // 配置属性和字段的映射关系
private Long custId; // 客户主键
/**
* @Column:配置属性和字段的映射关系
* name:数据库表中字段的名称
* unique:是否唯一
* nullable:是否可以为空
* inserttable:是否可以插入
* updateable:是否可以更新
* columnDefinition: 定义建表时创建此列的DDL
* secondaryTable: 从表名。如果此列不建在主表上(默认建在主表),
* 该属性定义该列所在从表的名字搭建开发环境
*/
@Column(name = "cust_name")
private String custName;//客户名称
@Column(name="cust_source")
private String custSource;//客户来源
@Column(name="cust_level")
private String custLevel;//客户级别
@Column(name="cust_industry")
private String custIndustry;//客户所属行业
@Column(name="cust_phone")
private String custPhone;//客户的联系方式
@Column(name="cust_address")
private String custAddress;//客户地址
/* get/set/toString方法请自动生成! */
}
三、抽离jpa工厂创建 JpaUtils
实体管理器工厂 是一个线程安全的类!通过静态代码块的形式,工具类在初始化时,创建一个公共的实体管理器工厂对象
package com.lingting.utils;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
public class JpaUtils {
private static EntityManagerFactory factory;
static {
// 加载配置文件, 创建工厂对象,指定【持久化单元】配置,见META-INF/persistence.xml文件
factory = Persistence.createEntityManagerFactory("myJpa");
}
/**
* 获取EntityManger对象: 完成持久化操作的核心对象
* 让实体类与底层数据源进行O/R映射管理:方法说明
* getTransaction : 获取事务对象
* persist : 保存操作
* merge : 更新操作
* remove : 删除操作
* find : 根据id查询:立即查询
* getReference : 根据id查询:延迟查询,
* 返回实体类pojo的一个代理对象;
* 没有真正查询;只是增强了各个get方法;(切面)
* 在执行get方法之前调用【一次数】据库查询!
* 之后的调用则不会去查询!
* 获取事务对象EntityTransaction:
* EntityManger对象.getTransaction():方法概述
* begin:开启事务
* commit:提交事务
* rollback:回滚事务
*/
public static EntityManager getEntityManager() {
return factory.createEntityManager();
}
/**
* 归还资源
*/
public static void close(EntityManager entityManager) {
entityManager.close();
}
/**
* 彻底关闭资源
* @param entityManager
*/
public static void closeFactory(EntityManager entityManager) {
close(entityManager);
factory.close();
}
}
四、jpa基本增删改查
1. JpaTest/JpqlTest测试类模板
package com.lingting.test;
import com.lingting.pojo.Customer;
import com.lingting.utils.JpaUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
/**
* 测试jpa 一般步骤
* 1. 加载配置文件, 创建 实体管理器工厂对象(线程安全,静态)【交给工具类完成】
* 2. 通过工厂获取实体类管理器
* 3. 获取事务,开启事务
* 4. 增删改查
* 5. 提交事务/回滚事务
* 6. 释放资源
*/
public class JpaTest { // 或者为JpqlTest类
private EntityManager em;
private EntityTransaction tx;
@Before
public void init() {
em = JpaUtils.getEntityManager();
tx = em.getTransaction();
}
/** 以下代码写在此处 */
}
2). 增
/**
* 增
*/
@Test
public void persist() {
// 开启事务
tx.begin();
// 创建实体类,进行保存
Customer customer = new Customer();
customer.setCustName("星环城");
customer.setCustIndustry("太空能源开发");
// 保存
em.persist(customer);
// 提交事务
tx.commit();
// 释放资源
em.close();
}
3). 改
/**
* 改: 根据id修改,null值会覆盖原有值
*/
@Test
public void merge() {
tx.begin();
Customer customer = em.find(Customer.class, 1L);
customer.setCustName("新星环城");
customer.setCustIndustry("太空能源开发");
// 清空缓存,把customer对象从缓存中清除
em.clear();
em.merge(customer);
tx.commit();
em.close();
}
4). 删
/**
* 删除:根据id进行删除
*/
@Test
public void remove() {
tx.begin();
try {
Customer customer = em.find(Customer.class, 1L);
/* ~~~~ 清空id,清空缓存,再删除会失败 ~~~~
customer.setCustId(null);
em.clear();
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
em.remove(customer);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
}
5). 查
/**
* 立即查询:根据id查询客户
* 查询不需要事务吧!
*/
@Test
public void find() {
// 第一个参数为pojo字节码对象,第二个为id
Customer customer = em.find(Customer.class, 1L);
System.out.println(customer);
em.close();
}
/**
* 懒加载(延迟加载):动态代理
* 返回pojo的动态代理类,在get等方法中配置查询数据库的切面,
* 只有第一次调用时回去查询!
*/
@Test
public void getReference() {
Customer reference = em.getReference(Customer.class, 2L);
System.out.println(reference);
em.close();
}
/**
* EntityManager 的实体缓存现象!
*/
@Test
public void cache() {
Customer c1 = em.find(Customer.class, 2L);
Customer c2 = em.find(Customer.class, 2L);
System.out.println(c1 == c2);
// 清空缓存,对象就不一样了!
em.clear();
Customer c3 = em.find(Customer.class, 3L);
System.out.println(c2 == c3);
}
五、jpql复杂查询 JpqlTest
test模板如上
四、...
1). 查询全部
/**
* 查询全部: jpql 不支持 `select *`
* sql:SELECT * FROM cst_customer
* jqpl:from cn.itcast.domain.Customer
* 或者简写包名
*/
@Test
public void findAll() {
String jpql = "from Customer";
// 创建Query查询对象,query对象才是指定jpql的对象
Query query = em.createQuery(jpql);
// 发送查询,并封装结果集
List list = query.getResultList();
list.forEach(System.out::println);
em.close();
}
2). 分页 + 倒叙
/**
* 分页查询 + 倒叙查询【先倒叙,后分页;如果要反过来,需要进行复杂查询,jpa的方式很麻烦】
* sql:select * from cst_customer ORDER BY cust_id DESC limit ?,?
* jqpl : from Customer order by custId desc
*/
@Test
public void pageQuery() {
String jpql = "from Customer order by custId desc";
Query query = em.createQuery(jpql);
// 偏移量 = (查询页码 - 1) * 每页条数
query.setFirstResult((2 - 1) * 2);
// 每页查询的条数
query.setMaxResults(2);
/**
* getResultList : 直接将查询结果封装为list集合
* getSingleResult : 得到唯一的结果集
*/
List list = query.getResultList();
list.forEach(System.out::println);
em.close();
}
3). 条件查询
/**
* 条件查询:以 “新” 开头的客户
* sql:SELECT * FROM cst_customer WHERE cust_name LIKE ?
* jpql : from Customer where custName like ?
*/
@Test
public void conditionQuery() {
String jpql = "from Customer where custName like ?";
Query query = em.createQuery(jpql);
// 设置参数
query.setParameter(1, "新%");
List list = query.getResultList();
list.forEach(System.out::println);
em.close();
}
3). 聚合查询
/**
* 聚合查询,统计总数
* sql:SELECT COUNT(cust_id) FROM cst_customer
* jpql:select count(custId) from Customer
*/
@Test
public void aggregationQuery() {
String jpql = "select count(custId) from Customer";
Query query = em.createQuery(jpql);
Object singleResult = query.getSingleResult();
System.out.println(singleResult);
em.close();
}