JPA的全称是Java Persistence API, 即Java持久化API,是SUN公司推出的一套基于ORM的规范,内部是由一系列的接口和抽象类构成。
JPA通过JDK 5.0注解描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。
优点:
①标准化
任何声称符合 JPA 标准的框架都遵循同样的架构,提供相同的访问API,这保证了基于JPA开发的企业应用能够经过少量的修改就能够在不同的JPA框架下运行。
②容器级特性支持
JPA框架中支持大数据集、事务、并发等容器级事务,这使得 JPA 超越了简单持久化框架的局限,在企业应用发挥更大的作用。
③简单方便
在JPA框架下创建实体和创建Java 类一样简单,没有任何的约束和限制,只需要使用 javax.persistence.Entity
进行注释,JPA的框架和接口也都非常简单,没有太多特别的规则和设计模式的要求,开发者可以很容易的掌握。
④ 查询能力
JPA的查询语言是面向对象而非面向数据库的,JPA定义了独特的JPQL(Java Persistence Query Language)
,它是针对实体的一种查询语言,操作对象是实体,能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。
⑤高级特性
JPA 中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,这样的支持能够让开发者最大限度的使用面向对象的模型设计企业应用,而不需要自行处理这些特性在关系数据库的持久化。
JPA与hibernate的关系
JPA规范本质上就是一种ORM规范,注意不是ORM框架——JPA并未提供ORM实现,它只是制订了规范,提供了编程的API接口,具体实现则由服务厂商来提供。
JPA和Hibernate的关系就像JDBC和JDBC驱动的关系,JPA是规范,Hibernate除了作为ORM框架之外,它也是一种JPA实现。如果使用JPA规范进行数据库操作,底层需要hibernate作为其实现类完成数据持久化工作。
创建maven工程,导入坐标
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.hibernate.version>5.0.12.Finalproject.hibernate.version>
properties>
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-entitymanagerartifactId>
<version>${project.hibernate.version}version>
dependency>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-c3p0artifactId>
<version>${project.hibernate.version}version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.44version>
dependency>
dependencies>
创建jpa核心配置文件
要求 必须在resources/META-INF目录下,文件名叫persistence.xml
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
<persistence-unit name="myJPA" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.jpa.HibernatePersistenceProviderprovider>
<properties>
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql:///test"/>
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="javax.persistence.jdbc.password" value=""/>
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="create"/>
properties>
persistence-unit>
persistence>
创建客户的数据库表
/*创建客户表*/
CREATE TABLE customer (
cust_id BIGINT(32) PRIMARY KEY 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 '客户联系电话'
);
创建对应数据库表的客户实体类
package com.pojo;
public class Customer {
private Long id;//主键
private String name;//名称
private String source;//来源
private String industry;//行业
private String level;//级别
private String address;//地址
private String phone;//联系方式
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
public String getIndustry() {
return industry;
}
public void setIndustry(String industry) {
this.industry = industry;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public String toString() {
return "Customer{" +
"id=" + id +
", name='" + name + '\'' +
", source='" + source + '\'' +
", industry='" + industry + '\'' +
", level='" + level + '\'' +
", address='" + address + '\'' +
", phone='" + phone + '\'' +
'}';
}
}
配置映射关系
实体类和表的关系
实体类中属性和表字段的映射关系
创建测试类
public class JpaTest {
//测试jpa的保存
@Test
public void save(){
//创建工厂对象
EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJPA");
//获取实体管理器
EntityManager manager = factory.createEntityManager();
//获取事务对象,开启事务
EntityTransaction transaction = manager.getTransaction();
transaction.begin();
//保存操作实现
Customer customer=new Customer();
customer.setName("kobe");
customer.setIndustry("nba");
manager.persist(customer);
//提交事务
transaction.commit();
//释放资源
manager.close();
factory.close();
}
}
自增 要求底层数据库支持自增 mysql
@GeneratedValue(strategy = GenerationType.IDENTITY)
序列 要求底层数据库支持序列 oracle
@GeneratedValue(strategy = GenerationType.SEQUENCE)
TABLE JPA提供的一种机制,通过一张数据库表完成自增
@GeneratedValue(strategy = GenerationType.TABLE)
由程序自动帮我们完成主键自增
@GeneratedValue(strategy = GenerationType.AUTO)
1.加载配置文件 创建工厂对象
静态方法 根据持久化单元名称创建实体管理器工厂
(创建工厂比较耗时,可以使用静态代码块创建一个公共的工厂)
2.通过工厂获取实体管理器EntityManager对象
是一个线程安全的对象,内部维护了数据库信息、缓存信息、所有实体管理器对象
EntityManager对象方法:
getTransaction 获取事务对象
persist 保存
merge 更新
remove 删除
find/getReference 根据id查询
3.获取事务对象,开启事务
EntityTransaction对象方法:
begin 开启事务
rollback 回滚事务
commit 提交事务
4.完成操作
5.提交(回滚)事务
6.释放资源
解决实体管理器工厂浪费资源和耗时问题,通过静态代码块的形式,当程序第一次访问工具类时,创建一个实体管理器工厂对象
public class JpaUtils {
private static EntityManagerFactory myJPAFactory;
static {
myJPAFactory = Persistence.createEntityManagerFactory("myJPA");
}
//获取实体管理器
public static EntityManager getEntityManager(){
return myJPAFactory.createEntityManager();
}
}
测试类修改为
//测试jpa的保存
@Test
public void save(){
//获取实体管理器
EntityManager manager = JpaUtils.getEntityManager();
//获取事务对象,开启事务
EntityTransaction transaction = manager.getTransaction();
transaction.begin();
//保存操作实现
Customer customer=new Customer();
customer.setName("kobe");
customer.setIndustry("nba");
manager.persist(customer);
//提交事务
transaction.commit();
//释放资源
manager.close();
}
//测试jpa的查询
@Test
public void findById(){
//获取实体管理器
EntityManager manager = JpaUtils.getEntityManager();
//获取事务对象,开启事务
EntityTransaction transaction = manager.getTransaction();
transaction.begin();
//查询操作实现(将配置中的create改为update)
Customer customer = manager.find(Customer.class, 1L);
System.out.println(customer);
//提交事务
transaction.commit();
//释放资源
manager.close();
}
使用getReference方法
//测试jpa的查询2
@Test
public void findById2(){
//获取实体管理器
EntityManager manager = JpaUtils.getEntityManager();
//获取事务对象,开启事务
EntityTransaction transaction = manager.getTransaction();
transaction.begin();
//查询操作实现(将配置中的create改为update)
Customer customer = manager.getReference(Customer.class, 1L);
System.out.println(customer);
//提交事务
transaction.commit();
//释放资源
manager.close();
}
find
方法查询,查询获取的对象就是当前客户对象本身,调用find方法时就会发送sql语句查询数据库getReference
方法查询,查询获取的对象是动态代理对象,调用方法时不会立即发送sql语句查询数据库,当调用结果对象时(本例中为输出对象)才会发送sql语句查询数据库//测试jpa的删除
@Test
public void remove(){
//获取实体管理器
EntityManager manager = JpaUtils.getEntityManager();
//获取事务对象,开启事务
EntityTransaction transaction = manager.getTransaction();
transaction.begin();
//删除操作实现(将配置中的create改为update)
Customer customer = manager.getReference(Customer.class, 1L);
manager.remove(customer);
//提交事务
transaction.commit();
//释放资源
manager.close();
}
//测试jpa的更新
@Test
public void update(){
//获取实体管理器
EntityManager manager = JpaUtils.getEntityManager();
//获取事务对象,开启事务
EntityTransaction transaction = manager.getTransaction();
transaction.begin();
//删除操作实现(将配置中的create改为update)
Customer customer = manager.getReference(Customer.class, 1L);
customer.setAddress("LOS ANGELES");
customer.setName("KOBE BRYANT");
manager.merge(customer);
//提交事务
transaction.commit();
//释放资源
manager.close();
}
JPQL全称Java persistence query language,Java持久化查询语言,是面向对象的查询语言,查询的是实体类和类中的属性,语法与SQL类似。
查询全部
//测试jpql查询
@Test
public void findAll(){
//获取实体管理器
EntityManager entityManager = JpaUtils.getEntityManager();
//获取事务对象,开启事务
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
//查询全部
String jpql="from com.pojo.Customer";
Query query = entityManager.createQuery(jpql);
List resultList = query.getResultList();
System.out.println(resultList);
//提交事务
transaction.commit();
//释放资源
entityManager.close();
}
排序查询
//测试jpql排序查询
@Test
public void findOrderById(){
//获取实体管理器
EntityManager entityManager = JpaUtils.getEntityManager();
//获取事务对象,开启事务
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
//根据id倒序查询全部
String jpql="from Customer order by id desc";
Query query = entityManager.createQuery(jpql);
List<Customer> resultList = query.getResultList();
for(Customer customer:resultList){
System.out.println(customer);
}
//提交事务
transaction.commit();
//释放资源
entityManager.close();
}
统计查询
//测试jpql统计查询
@Test
public void findCount(){
//获取实体管理器
EntityManager entityManager = JpaUtils.getEntityManager();
//获取事务对象,开启事务
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
//统计查询
String jpql="select count(id) from Customer";
Query query = entityManager.createQuery(jpql);
Long count = (Long) query.getSingleResult();
System.out.println("记录数: "+count);
//提交事务
transaction.commit();
//释放资源
entityManager.close();
}
分页查询
//测试jpql分页查询
@Test
public void findPage(){
//获取实体管理器
EntityManager entityManager = JpaUtils.getEntityManager();
//获取事务对象,开启事务
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
//分页查询
String jpql="from Customer";
Query query = entityManager.createQuery(jpql);
//对分页参数赋值 从1开始,查询1条
query.setFirstResult(1);
query.setMaxResults(1);
List<Customer> resultList = query.getResultList();
for(Customer customer:resultList){
System.out.println(customer);
}
//提交事务
transaction.commit();
//释放资源
entityManager.close();
}
条件查询
//测试jpql条件查询
@Test
public void findCondition(){
//获取实体管理器
EntityManager entityManager = JpaUtils.getEntityManager();
//获取事务对象,开启事务
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
//条件查询 姓名以KO开头的记录
String jpql="from Customer where name like ?";
Query query = entityManager.createQuery(jpql);
query.setParameter(1,"KO%");
List<Customer> resultList = query.getResultList();
for(Customer customer:resultList){
System.out.println(customer);
}
//提交事务
transaction.commit();
//释放资源
entityManager.close();
}
创建工程导入坐标
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<spring.version>5.2.1.RELEASEspring.version>
<hibernate.version>5.0.12.Finalhibernate.version>
properties>
<dependencies>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.4version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-aopartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-contextartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-ormartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-beansartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-testartifactId>
<version>${spring.version}version>
dependency>
<dependency>
<groupId>org.springframework.datagroupId>
<artifactId>spring-data-jpaartifactId>
<version>2.2.1.RELEASEversion>
dependency>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-coreartifactId>
<version>${hibernate.version}version>
dependency>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-entitymanagerartifactId>
<version>${hibernate.version}version>
dependency>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-validatorartifactId>
<version>5.3.5.Finalversion>
dependency>
<dependency>
<groupId>org.hibernategroupId>
<artifactId>hibernate-c3p0artifactId>
<version>${hibernate.version}version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>5.1.44version>
dependency>
<dependency>
<groupId>javax.elgroupId>
<artifactId>javax.el-apiartifactId>
<version>2.2.4version>
dependency>
<dependency>
<groupId>org.glassfish.webgroupId>
<artifactId>javax.elartifactId>
<version>2.2.4version>
dependency>
dependencies>
配置spring文件
在resources目录下创建一个spring-data-jpa.xml的springConfig配置文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="packagesToScan" value="com.pojo"/>
<property name="persistenceProvider">
<bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
property>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="generateDdl" value="false"/>
<property name="database" value="MYSQL"/>
<property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"/>
<property name="showSql" value="true"/>
bean>
property>
<property name="jpaDialect">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
property>
bean>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql:///test"/>
<property name="user" value="root"/>
<property name="password" value=""/>
bean>
<jpa:repositories base-package="com.dao" transaction-manager-ref="transactionManager"
entity-manager-factory-ref="entityManagerFactory"/>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
bean>
<context:component-scan base-package="com"/>
beans>
编写实体类,用jpa注解配置映射关系
跟之前的Customer类一样
@Entity//声明此类是一个实体类
@Table(name = "customer")//配置实体类和表的映射关系 name属性配置数据库表名
public class Customer {
@Id//声明主键
@GeneratedValue(strategy = GenerationType.IDENTITY)//配置主键生产策略
@Column(name = "cust_id")//配置属性和字段的映射关系 name配置数据库字段名
private Long id;//主键
@Column(name = "cust_name")
private String name;//名称
@Column(name = "cust_source")
private String source;//来源
@Column(name = "cust_industry")
private String industry;//行业
@Column(name = "cust_level")
private String level;//级别
@Column(name = "cust_address")
private String address;//地址
@Column(name = "cust_phone")
private String phone;//联系方式
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
public String getIndustry() {
return industry;
}
public void setIndustry(String industry) {
this.industry = industry;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public String toString() {
return "Customer{" +
"id=" + id +
", name='" + name + '\'' +
", source='" + source + '\'' +
", industry='" + industry + '\'' +
", level='" + level + '\'' +
", address='" + address + '\'' +
", phone='" + phone + '\'' +
'}';
}
}
dao层规范:
接口代码
public interface CustomerDao extends JpaRepository<Customer,Long> {
//不需要其他代码
//JpaRepository封装了基本CRUD操作
}
查询操作
直接调用findById方法根据Id查询
@RunWith(SpringJUnit4ClassRunner.class)//声明spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:spring-data-jpa-config.xml")//指定spring容器的配置信息
public class SpringDataJpaTest {
@Autowired
private CustomerDao customerDao;
@Test
public void findById(){
Optional<Customer> customer = customerDao.findById(2L);
System.out.println(customer);
}
}
保存和更新操作
@Test
public void save(){
//如果存在的对象id主键不存在,代表保存,存在代表更新
Customer customer=new Customer();
customer.setName("AAA");
customer.setIndustry("FootBall");
customerDao.save(customer);//保存
customer.setId(4L);
customer.setName("BBB");
customerDao.save(customer);//更新
}
删除操作
@Test
public void delete(){
customerDao.deleteById(4L);
}
查询所有操作
@Test
public void findAll(){
List<Customer> customers = customerDao.findAll();
for(Customer customer:customers){
System.out.println(customer);
}
}
执行过程分析
在自定义的CustomerDao中,并没有提供任何方法就可以使用其中的很多方法。
由于继承了JpaRepository所以我们可以使用这两个接口的所有方法。
但是这些方法都只是一些声明,没有具体的实现方式,那么在 Spring Data JPA中它又是怎么实现的呢?
以debug断点调试的方式,分析Spring Data JPA的的执行过程,以findById方法为例进行分析。
可以发现注入的customerDao
对象,本质上是通过JdkDynamicAopProxy
生成的一个代理对象:
当程序执行的时候,会通过JdkDynamicAopProxy
的invoke
方法,对customerDao
对象生成动态代理对象,这个动态代理对象就是SimpleJpaRepository
调用自定义类的findById
方法实际就是调用SimpleJpaRepository
的findById
方法
Spring Data JPA对JPA操作进行了进一步封装,简化了Dao层代码的开发。
执行过程和原理总结:
JdkDynamicAopProxy
的invoke
方法创建了一个动态代理对象SimpleJpaRepository
SimpleJpaRepository
中封装了JPA的操作hibernate
(封装了JDBC)完成数据库操作统计总数
@Test
public void count(){
long count = customerDao.count();
System.out.println(count);
}
判断存在
@Test
public void exists(){
boolean b = customerDao.existsById(3L);
System.out.println(b);
}
getOne() 查询一个
@Test@Transactional//保证正常运行
public void getOne(){
Customer customer = customerDao.getOne(1L);
System.out.println(customer);
}
通过源码getReference
方法可以看出getOne
和findById
的区别是使用了延迟加载
需要将JPQL通过注解配置到接口方法上
根据名称查询
接口中增加方法
public interface CustomerDao extends JpaRepository<Customer,Long> {
//根据客户名称查询
@Query(value = "from Customer where name = ?1")
Customer findByName(String name);
}
测试类中增加方法
@Test
public void findByName(){
Customer customer = customerDao.findByName("JAMES");
System.out.println(customer);
}
根据id和名称查询
接口中增加方法 占位符需要和方法参数的顺序一致, ?
后的数字是参数的索引,从1开始
//根据客户id和名称查询
@Query(value = "from Customer where id=?1 and name = ?2")
//等价于@Query(value = "from Customer where name=?2 and id = ?1")
Customer findByIdAndName(Long id,String name);
测试类中增加方法
@Test
public void findByIdAndName(){
Customer customer = customerDao.findByIdAndName(2L,"JAMES");
System.out.println(customer);
}
更新操作
springdatajpa的更新/删除操作需要事务支持(添加注解),默认执行结束后回滚,需要添加注解取消回滚
接口方法
//根据id更新名称
@Query(value = "update Customer set name=?1 where id=?2")
@Modifying//表示是更新操作
void updateNameById(String name,Long id);
测试方法
@Test
@Transactional//添加事务支持
@Rollback(false)//取消回滚
public void updateNameById(){
customerDao.updateNameById("JAMES23",2L);
}
需要将SQL通过注解配置到接口方法上
使用@Query
中的nativeQuery
属性进行标识(true表示MySQL)
查询全部
//查询全部
@Query(value = "select * from customer",nativeQuery = true)
List<Object[]> findAllBySql();
测试方法
@Test
public void findAllBySql(){
List<Object[]> lists = customerDao.findAllBySql();
for(Object[] obj:lists){
System.out.println(Arrays.toString(obj));
}
}
模糊查询
接口中添加方法
//模糊查询
@Query(value = "select * from customer where cust_name like ?",nativeQuery = true)
List<Object[]> findByNameWithSql(String name);
测试方法
@Test
public void findByNameWithSql(){
List<Object[]> lists = customerDao.findByNameWithSql("K%");
for(Object[] obj:lists){
System.out.println(Arrays.toString(obj));
}
}
是对JPQL更深层的封装,只需要按照springdataJPA提供的方法名规则定义方法,不需要配置JPQL语句
方法名的规则:
具体查询
findBy+对象中的属性名称
例如根据名称查询
测试方法
结果
条件查询
findBy+对象中的属性名称+查询方式
例:模糊查询
多条件查询
findBy+对象中的属性名称+查询方式+多条件连接符(and|or)
例:根据名称模糊匹配和所属行业精准匹配
接口方法:
Customer findByNameLikeAndIndustry(String name,String industry);
测试方法
@Test
public void findByNameLikeAndIndustry(){
Customer customer = customerDao.findByNameLikeAndIndustry("JA%","nba");
System.out.println(customer);
}
方法列表
T findOne(Specification
查询单个对象
List
查询列表
Page
分页查询全部
List
排序查询全部
long count(Specification
统计查询
Specification
代表查询条件
查询单个
dao接口需要继承JpaSpecificationExecutor
测试方法
//根据客户名称查询
@Test
public void findOne(){
//自定义查询条件 实现Specification接口 实现toPredicate方法 从root获取对象属性 CriteriaBuilder构造查询条件
Specification<Customer> specification= (Specification<Customer>) (root, criteriaQuery, criteriaBuilder) -> {
//获取比较的属性
Path<Object> name = root.get("name");
//构造查询
Predicate res = criteriaBuilder.equal(name, "JAMES23");//精确匹配,相当于查询name为JAMES23的记录
return res;
};
Optional<Customer> customer = customerDao.findOne(specification);
System.out.println(customer);
}
多条件查询
//根据客户名称和行业查询
@Test
public void findOne2(){
Specification<Customer> specification= (Specification<Customer>) (root, criteriaQuery, criteriaBuilder) -> {
//获取比较的属性
Path<Object> name = root.get("name");
Path<Object> industry = root.get("industry");
//构造查询
Predicate res1 = criteriaBuilder.equal(name, "JAMES23");
Predicate res2 = criteriaBuilder.equal(industry, "nba");
Predicate res = criteriaBuilder.and(res1, res2);
return res;
};
Optional<Customer> customer = customerDao.findOne(specification);
System.out.println(customer);
}
模糊查询
//根据客户名称模糊查询
@Test
public void findOne3(){
Specification<Customer> specification= (Specification<Customer>) (root, criteriaQuery, criteriaBuilder) -> {
//获取比较的属性
Path<Object> name = root.get("name");
//构造查询 使用path对象的as方法指定类型
Predicate res = criteriaBuilder.like(name.as(String.class),"JA%");
return res;
};
Optional<Customer> customer = customerDao.findOne(specification);
System.out.println(customer);
}
排序查询
使用Sort.by()
方法进行排序,将返回值作为查询的参数
@Test
public void findOne3(){
Specification<Customer> specification= (Specification<Customer>) (root, criteriaQuery, criteriaBuilder) -> {
//获取比较的属性
Path<Object> name = root.get("name");
//构造查询 使用path对象的as方法指定类型
Predicate res = criteriaBuilder.like(name.as(String.class),"JA%");
return res;
};
//第一个参数排序的顺序 第二个参数排序的属性名
Sort sort=Sort.by(Sort.Direction.ASC,"id");
List<Customer> customer = customerDao.findAll(specification,sort);
System.out.println(customer);
}
分页查询
@Test
public void findOne4(){
//第一个参数查询的页数 第二个参数显示的个数
Specification<Customer> specification=null;
Pageable pageable=PageRequest.of(1,1);
Page<Customer> page = customerDao.findAll(specification, pageable);
System.out.println("数据"+page.getContent());
System.out.println("page"+page.getTotalPages());//总页数
System.out.println("count"+page.getTotalElements());//总条数
}
表关系
实体类关系
步骤
案例:客户(一)和联系人(多)
1 明确表关系:一对多
2 确定表关系
主表:客户 使用之前的客户表
从表:联系人 在从表添加外键
/*创建联系人表*/
CREATE TABLE linkman (
lkm_id BIGINT(32) PRIMARY KEY AUTO_INCREMENT COMMENT '联系人编号(主键)',
lkm_name VARCHAR(16) COMMENT '联系人姓名',
lkm_gender CHAR(1) COMMENT '联系人性别',
lkm_phone VARCHAR(16) COMMENT '联系人办公电话',
lkm_mobile VARCHAR(16) COMMENT '联系人手机',
lkm_email VARCHAR(64) COMMENT '联系人邮箱',
lkm_position VARCHAR(16) COMMENT '联系人职位',
lkm_memo VARCHAR(512) COMMENT '联系人备注',
lkm_cust_id BIGINT(32) NOT NULL COMMENT '客户id(外键)',
CONSTRAINT `FK_cst_linkman_lkm_cust_id` FOREIGN KEY (`lkm_cust_id`) REFERENCES `customer` (`cust_id`)
);
3 编写实体类
客户:在客户实体类中包含一个联系人集合
@Entity//声明此类是一个实体类
@Table(name = "customer")//配置实体类和表的映射关系 name属性配置数据库表名
public class Customer {
@Id//声明主键
@GeneratedValue(strategy = GenerationType.IDENTITY)//配置主键生产策略
@Column(name = "cust_id")//配置属性和字段的映射关系 name配置数据库字段名
private Long id;//主键
@Column(name = "cust_name")
private String name;//名称
@Column(name = "cust_source")
private String source;//来源
@Column(name = "cust_industry")
private String industry;//行业
@Column(name = "cust_level")
private String level;//级别
@Column(name = "cust_address")
private String address;//地址
@Column(name = "cust_phone")
private String phone;//联系方式
//配置一对多关系 targetEntity 多实体类的字节码
@OneToMany(targetEntity = Linkman.class)
//name 从表外键 referencedColumnName 主表主键
@JoinColumn(name ="lkm_cust_id" ,referencedColumnName = "cust_id")
private Set<Linkman> linkmen=new HashSet<>();
public Set<Linkman> getLinkmen() {
return linkmen;
}
public void setLinkmen(Set<Linkman> linkmen) {
this.linkmen = linkmen;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSource() {
return source;
}
public void setSource(String source) {
this.source = source;
}
public String getIndustry() {
return industry;
}
public void setIndustry(String industry) {
this.industry = industry;
}
public String getLevel() {
return level;
}
public void setLevel(String level) {
this.level = level;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public String toString() {
return "Customer{" +
"id=" + id +
", name='" + name + '\'' +
", source='" + source + '\'' +
", industry='" + industry + '\'' +
", level='" + level + '\'' +
", address='" + address + '\'' +
", phone='" + phone + '\'' +
'}';
}
}
联系人:在联系人实体类包含一个客户对象
@Entity
@Table(name="linkman")
public class Linkman {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "lkm_id")
private Long id;
@Column(name ="lkm_name" )
private String name;
@Column(name = "lkm_gender")
private String gender;
@Column(name = "lkm_phone")
private String phone;
@Column(name = "lkm_mobile")
private String mobile;
@Column(name = "lkm_email")
private String email;
@Column(name = "km_position")
private String position;
@Column(name = "km_memo")
private String memo;
//配置表关系
@ManyToOne(targetEntity = Customer.class)
//配置外键
@JoinColumn(name ="lkm_cust_id" ,referencedColumnName = "cust_id")
private Customer customer;
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPosition() {
return position;
}
public void setPosition(String position) {
this.position = position;
}
public String getMemo() {
return memo;
}
public void setMemo(String memo) {
this.memo = memo;
}
@Override
public String toString() {
return "Linkman{" +
"id=" + id +
", name='" + name + '\'' +
", gender='" + gender + '\'' +
", phone='" + phone + '\'' +
", mobile='" + mobile + '\'' +
", email='" + email + '\'' +
", position='" + position + '\'' +
", memo='" + memo + '\'' +
'}';
}
}
4 配置映射关系
使用JPA注解配置一对多关系
编写联系人的dao接口
public interface LinkmanDao extends JpaRepository<Linkman,Long>, JpaSpecificationExecutor<Linkman> {
}
在配置文件加入以下配置
<property name="jpaProperties">
<props>
<prop key="hibernate.hbm2ddl.auto">createprop>
props>
property>
编写测试类测试
@RunWith(SpringJUnit4ClassRunner.class)//声明spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:spring-data-jpa-config.xml")
public class OneToManyTest {
@Autowired
private CustomerDao customerDao;
@Autowired
private LinkmanDao linkmanDao;
@Test
@Transactional
@Rollback(false)//不自动回滚
public void save(){
Customer customer=new Customer();
customer.setName("kobe");
Linkman linkman=new Linkman();
linkman.setName("james");
customer.getLinkmen().add(linkman);
customerDao.save(customer);
linkmanDao.save(linkman);
}
}
也可通过联系人维护外键关系
@Test
@Transactional
@Rollback(false)//不自动回滚
public void save(){
Customer customer=new Customer();
customer.setName("kobe");
Linkman linkman=new Linkman();
linkman.setName("james");
linkman.setCustomer(customer);
customerDao.save(customer);
linkmanDao.save(linkman);
}
从显示的sql语句中看到少了一条update语句,数据库结果是一样的
删除从表数据:可以任意删除。
删除主表数据:
有从表数据
1、在默认情况下,它会把外键字段置为null,然后删除主表数据。如果在数据库的表 结构上,外键字段有非空约束,默认情况就会报错了。
2、如果配置了放弃维护关联关系的权利,则不能删除(与外键字段是否允许为null, 没有关系)因为在删除时,它根本不会去更新从表的外键字段了。
3、如果还想删除,使用级联删除引用
没有从表数据引用:随便删
级联操作:指操作一个对象同时操作它的关联对象
使用方法:只需要在操作主体的注解上配置cascade
级联添加
在Customer类的联系人集合属性上的一对多注解配置cascade属性
测试类
@Test
@Transactional
@Rollback(false)//不自动回滚
public void add(){
Customer customer=new Customer();
customer.setName("kobe");
Linkman linkman=new Linkman();
linkman.setName("james");
Linkman linkman2=new Linkman();
linkman2.setName("gigi");
customer.getLinkmen().add(linkman);
customer.getLinkmen().add(linkman2);
linkman.setCustomer(customer);
linkman2.setCustomer(customer);
//只需要保存客户,即可保存联系人
customerDao.save(customer);
}
查询数据库的联系人表
级联删除
先修改配置文件自动创建表为update,这样不会在运行时重新创建表清空数据
测试方法
@Test
@Transactional
@Rollback(false)//不自动回滚
public void delete(){
customerDao.deleteById(1L);
}
cascade属性
CascadeType.MERGE 级联更新
CascadeType.PERSIST 级联保存:
CascadeType.REFRESH 级联刷新:
CascadeType.REMOVE 级联删除:
CascadeType.ALL 包含所有
案例:用户和角色
用户类
@Entity
@Table(name = "user")
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "user_id")
private Long id;
@Column(name = "user_name")
private String name;
@ManyToMany(targetEntity = Role.class)
@JoinTable(name = "user_role",//中间表名称
joinColumns = {@JoinColumn(name = "user_key",referencedColumnName = "user_id")},//当前对象在中间表的外键
inverseJoinColumns = {@JoinColumn(name ="role_key" ,referencedColumnName ="role_id" )})//对方对象在中间表的外键
private Set<Role> roles=new HashSet<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
}
角色类
@Entity
@Table(name = "role")
public class Role implements Serializable {
@Id@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "role_id")
private Long id;
@Column(name = "role_name")
private String name;
@ManyToMany(targetEntity = User.class)
@JoinTable(name = "user_role",//中间表名称
joinColumns = {@JoinColumn(name = "role_key",referencedColumnName = "role_id")},//当前对象在中间表的外键
inverseJoinColumns = {@JoinColumn(name ="user_key" ,referencedColumnName ="user_id" )})//对方对象在中间表的外键
private Set<User> users=new HashSet<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<User> getUsers() {
return users;
}
public void setUsers(Set<User> users) {
this.users = users;
}
}
测试类
@RunWith(SpringJUnit4ClassRunner.class)//声明spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:spring-data-jpa-config.xml")
public class ManyToMany {
@Autowired
private UserDao userDao;
@Autowired
private RoleDao roleDao;
@Test@Transactional@Rollback(false)
public void add(){
User user=new User();
user.setName("kobe");
Role role=new Role();
role.setName("basketball-player");
user.getRoles().add(role);
userDao.save(user);
roleDao.save(role);
}
}
在多对多(保存)中,如果双向都设置关系,意味着双方都维护中间表,都会往中间表插入数据,中间表的2个字段又作为联合主键,所以报错,主键重复,解决保存失败的问题:只需要在任意一方放弃对中间表的维护权即可,推荐在被动的一方放弃,配置如下:
修改Role类中的属性
@ManyToMany(mappedBy = "roles")
private Set<User> users=new HashSet<>();
级联操作
在User类的多读多注解添加cascade属性
测试方法
@Test@Transactional@Rollback(false)
public void addCascade(){
User user=new User();
user.setName("kobe");
Role role=new Role();
role.setName("basketball-player");
user.getRoles().add(role);
userDao.save(user);//只需要保存用户
}
运行,查看数据库,role表插入记录
删除记录,先将配置文件中创建数据库方式设置为update
@Test@Transactional@Rollback(false)
public void delete(){
userDao.deleteById(1L);
}
含义:查询一个对象(get方法查询)的同时,通过此对象可以查询它的关联对象
案例:客户和联系人(一对多)
从客户查询联系人
@RunWith(SpringJUnit4ClassRunner.class)//声明spring提供的单元测试环境
@ContextConfiguration(locations = "classpath:spring-data-jpa-config.xml")
public class QueryTest {
@Autowired
private CustomerDao customerDao;
@Autowired
private LinkmanDao linkmanDao;
@Test@Transactional
public void test(){
Customer customer = customerDao.getOne(1L);
Set<Linkman> linkmen = customer.getLinkmen();
for (Linkman linkman : linkmen) {
System.out.println(linkman);
}
}
}
通过客户对象可直接查询联系人
对象导航查询一到多默认使用延迟加载的形式查询,如果要使用立即加载,在实体类注解配置加上fetch属性。
从联系人查询客户
@Test@Transactional
public void test2(){
Linkman linkman = linkmanDao.getOne(1L);
Customer customer = linkman.getCustomer();
System.out.println(customer);
}
总结
从一查多 默认使用延迟加载 关联对象是集合 使用立即加载可能浪费资源
从多查一 默认使用立即加载 关联对象是一个对象 所以使用立即加载