author:尹会东start
createTime:2020-06-04
文章目录
一,orm思想和hibernate以及jpa
1.orm思想
主要目的:操作实体类就相当于操作数据库表
建立两个映射关系
实体类
和 表
实体类中的属性
和 表中字段的映射关系
不再重点关注: SQL语句
实现了ORM思想的框架:mybatis
,hibernate
2. hibernate框架介绍
Hibernate是一个开放源代码的对象关系映射框架
,它对JDBC进行了非常轻量级的对象封装,它将POJO
与数据库表
建立映射关系,是一个全自动的orm框架
,hibernate可以自动生成SQL语句,自动执行,使得Java程序员可以随心所欲的使用对象编程思维
来操纵数据库。
3.jpa规范
JPA的全称是Java Persistence API, 即Java 持久化API,是SUN公司推出的一套基于ORM的规范,内部是由一系列的接口和抽象类构成。
JPA通过JDK 5.0注解描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。
JPA与hibernate的关系
JPA和Hibernate的关系就像JDBC和JDBC驱动的关系,JPA是规范,Hibernate除了作为ORM框架之外,它也是一种JPA实现。JPA怎么取代Hibernate呢?JDBC规范可以驱动底层数据库吗?答案是否定的,也就是说,如果使用JPA规范进行数据库操作,底层需要hibernate作为其实现类完成数据持久化工作。
4.jpa的入门案例
1.案例:是客户的相关操作(增删改查)
客户:就是一家公司
2.创建项目导入依赖
<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>
3.配置文件
位置:配置到类路径下的一个叫做 META-INF
的文件夹下
命名:persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
<!--配置持久化单元
name:持久化单元名称
transaction-type:事务类型
RESOURCE_LOCAL:本地事务管理
JTA:分布式事务管理 -->
<persistence-unit name="myJpa" transaction-type="RESOURCE_LOCAL">
<!--配置JPA规范的服务提供商 -->
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<properties>
<!-- 数据库驱动 -->
<property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/>
<!-- 数据库地址 -->
<property name="javax.persistence.jdbc.url" value="jdbc:mysql://localhost:3306/jpa"/>
<!-- 数据库用户名 -->
<property name="javax.persistence.jdbc.user" value="root"/>
<!-- 数据库密码 -->
<property name="javax.persistence.jdbc.password" value="root"/>
<!--jpa提供者的可选配置:我们的JPA规范的提供者为hibernate,所以jpa的核心配置中兼容hibernate的配 -->
<!--显示sql语句-->
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.format_sql" value="true"/>
<!--
建表方式
create:每次执行都创建表
update:没有表才创建
none:不创建表
-->
<property name="hibernate.hbm2ddl.auto" value="update"/>
</properties>
</persistence-unit>
</persistence>
4.实体类
/**
* @author yinhuidong
* @createTime 2020-06-01-21:43
*/
//作用:指定当前类是实体类。
@Entity
/**
* 作用:指定实体类和表之间的对应关系。
* 属性:
* name:指定数据库表的名称
*/
@Table(name = "t_person")
public class Person {
//作用:指定当前字段是主键。
/**
* @Id
* 作用:指定主键的生成方式。。
* 属性:
* strategy :指定主键生成策略。
*/
@GeneratedValue(strategy = GenerationType.IDENTITY)
/**
* @Column
* 作用:指定实体类属性和数据库表之间的对应关系
* 属性:
* name:指定数据库表的列名称。
* unique:是否唯一
* nullable:是否可以为空
* inserttable:是否可以插入
* updateable:是否可以更新
* columnDefinition: 定义建表时创建此列的DDL
* secondaryTable: 从表名。如果此列不建在主表上(默认建在主表),
* 该属性定义该列所在从表的名字搭建开发环境[重点]
*/
@Column(name = "id")
private Integer id;
@Column(name = "p_name")
private String name;
@Column(name = "p_age")
private Integer age;
@Column(name = "p_email")
private String email;
@Column(name = "p_birth")
private Date birth;
public Person() {
}
public Person(Integer id, String name, Integer age, String email, Date birth) {
this.id = id;
this.name = name;
this.age = age;
this.email = email;
this.birth = birth;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Date getBirth() {
return birth;
}
public void setBirth(Date birth) {
this.birth = birth;
}
@Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", email='" + email + '\'' +
", birth=" + birth +
'}';
}
}
5.测试类
jpa操作的操作步骤
1.加载配置文件创建实体管理器工厂
Persisitence:静态方法(根据持久化单元名称创建实体管理器工厂)
createEntityMnagerFactory(持久化单元名称)
作用:创建实体管理器工厂
2.根据实体管理器工厂,创建实体管理器
EntityManagerFactory :获取EntityManager对象
方法:createEntityManager
* 内部维护的很多的内容
内部维护了数据库信息,
维护了缓存信息
维护了所有的实体管理器对象
再创建EntityManagerFactory的过程中会根据配置创建数据库表
* EntityManagerFactory的创建过程比较浪费资源
特点:线程安全的对象
多个线程访问同一个EntityManagerFactory不会有线程安全问题
* 如何解决EntityManagerFactory的创建过程浪费资源(耗时)的问题?
思路:创建一个公共的EntityManagerFactory的对象
* 静态代码块的形式创建EntityManagerFactory
3.创建事务对象,开启事务
EntityManager对象:实体类管理器
beginTransaction : 创建事务对象
presist : 保存
merge : 更新
remove : 删除
find/getRefrence : 根据id查询
Transaction 对象 : 事务
begin:开启事务
commit:提交事务
rollback:回滚
4.增删改查操作
5.提交事务
6.释放资源
测试
@Test
public void testSave(){
EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa");
EntityManager em = factory.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
Person person = new Person(null,"aaa",22,"aaa@163.com",new Date());
em.persist(person);
tx.commit();
}
5.主键生成策略
@GeneratedValue
:配置主键的生成策略
strategy
GenerationType.IDENTITY
:自增,mysql
底层数据库必须支持自动增长(底层数据库支持的自动增长方式,对id自增)
GenerationType.SEQUENCE
: 序列,oracle
底层数据库必须支持序列
GenerationType.TABLE
: jpa提供的一种机制,通过一张数据库表的形式帮助我们完成主键自增
GenerationType.AUTO
: 由程序自动的帮助我们选择主键生成策略
6.JPA的API介绍
1.Persistence对象
Persistence
对象主要作用是用于获取EntityManagerFactory
对象的 。通过调用该类的createEntityManagerFactory
静态方法,根据配置文件中持久化单元名称创建EntityManagerFactory
。
EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa");
2.EntityManagerFactory
EntityManagerFactory
接口主要用来创建 EntityManager
实例
EntityManager em = factory.createEntityManager();
由于EntityManagerFactory
是一个线程安全的对象(即多个线程访问同一个EntityManagerFactory
对象不会有线程安全问题),并且EntityManagerFactory
的创建极其浪费资源,所以在使用JPA编程时,我们可以对EntityManagerFactory
的创建进行优化,只需要做到一个工程只存在一个EntityManagerFactory
即可。
3. EntityManager
在 JPA 规范中,EntityManager
是完成持久化操作的核心对象。实体类作为普通 java对象,只有在调用 EntityManager
将其持久化后才会变成持久化对象。EntityManager
对象在一组实体类与底层数据源之间进行 O/R 映射的管理。它可以用来管理和更新 Entity Bean
, 根椐主键查找 Entity Bean
, 还可以通过JPQL语句查询实体。我们可以通过调用EntityManager
的方法完成获取事务,以及持久化数据库的操作
getTransaction : 获取事务对象
persist : 保存操作
merge : 更新操作
remove : 删除操作
find/getReference : 根据id查询
4.EntityTransaction
在 JPA 规范中, EntityTransaction
是完成事务操作的核心对象,对于EntityTransaction
在我们的java代码中承接的功能比较简单
begin:开启事务
commit:提交事务
rollback:回滚事务
7. 抽取JPAUtil工具类
/**
* @author yinhuidong
* @createTime 2020-06-02-0:13
*/
public class JpaUtils {
private static EntityManagerFactory factory;
static {
factory = Persistence.createEntityManagerFactory("myJpa");
}
public static EntityManager getEm(){
return factory.createEntityManager();
}
}
8. 使用JPA完成增删改查操作
1.保存
/**
* 添加
*/
@Test
public void testSave(){
EntityManager em = JpaUtils.getEm();
EntityTransaction tx = em.getTransaction();
tx.begin();
Person person = new Person(null,"aaa",22,"aaa@163.com",new Date());
em.persist(person);
tx.commit();
em.close();
}
2.删除
/**
* 删除:remove
*/
@Test
public void testRemove(){
EntityManager em = JpaUtils.getEm();
EntityTransaction tx = em.getTransaction();
tx.begin();
Person person = em.getReference(Person.class, 1);
em.remove(person);
tx.commit();
em.close();
}
3.更新
/**
* 更新:merge
*/
@Test
public void testmerge(){
EntityManager em = JpaUtils.getEm();
EntityTransaction tx = em.getTransaction();
tx.begin();
Person person = new Person(1,"bbb",22,"aaa@163.com",new Date());
em.merge(person);
tx.commit();
em.close();
}
4.根据ID查询
1)立即加载
/**
* 查询:find
* 立即加载,执行find()立刻发出sql
*/
@Test
public void testFind(){
EntityManager em = JpaUtils.getEm();
Person person = em.find(Person.class, 1);
System.out.println(person);
em.close();
}
2)懒加载
/**
* 查询:getReference
* 懒加载机制,什么时候调用查询结果什么时候发出sql语句
* 得到的是一个动态代理对象
*/
@Test
public void testgetReference(){
EntityManager em = JpaUtils.getEm();
Person person = em.getReference(Person.class, 1);
System.out.println(person);
em.close();
}
5.复杂查询
JPQL全称Java Persistence Query Language
Java持久化查询语言(JPQL)是一种可移植的查询语言,旨在以面向对象表达式语言的表达式,将SQL语法和简单查询语义绑定在一起·使用这种语言编写的查询是可移植的,可以被编译成所有主流数据库服务器上的SQL。
其特征与原生SQL语句类似,并且完全面向对象,通过类名和属性访问,而不是表名和表的属性
1)查询全部
/**
* 查询所有
*/
@Test
public void test1(){
EntityManager em = JpaUtils.getEm();
String jpql ="from Person";
Query query = em.createQuery(jpql);
query.getResultList().forEach(System.out::println);
em.close();
}
2)分页查询
/**
* 分页查询
*/
@Test
public void test1(){
EntityManager em = JpaUtils.getEm();
String jpql ="from Person";
Query query = em.createQuery(jpql);
query.setFirstResult(0);
query.setMaxResults(2);
query.getResultList().forEach(System.out::println);
em.close();
}
3)条件查询
/**
* 条件查询
*/
@Test
public void test1(){
EntityManager em = JpaUtils.getEm();
String jpql ="from Person where name like ?";
Query query = em.createQuery(jpql);
query.setParameter(1,"aa%");
//获取唯一结果集
//System.out.println(query.getSingleResult());
query.getResultList().forEach(System.out::println);
em.close();
}
4)排序查询
/**
* 排序查询
*/
@Test
public void test1(){
EntityManager em = JpaUtils.getEm();
String jpql ="from Person order by id desc";
Query query = em.createQuery(jpql);
query.getResultList().forEach(System.out::println);
em.close();
}
5)统计查询
/**
* 统计查询
*/
@Test
public void test1(){
EntityManager em = JpaUtils.getEm();
String jpql ="select count(id) from Person";
Query query = em.createQuery(jpql);
System.out.println(query.getSingleResult());
em.close();
}
二,springdatajpa的运行原理以及基本操作
1.springDataJpa概述
1.1SpringDataJpa概述
Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套JPA应用框架,可使开发者用极简的代码即可实现对数据库的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使用 Spring Data JPA 可以极大提高开发效率!
Spring Data JPA 让我们解脱了DAO层的操作,基本上所有CRUD都可以依赖于它来实现,在实际的工作工程中,推荐使用Spring Data JPA + ORM(如:hibernate)完成操作,这样在切换不同的ORM框架时提供了极大的方便,同时也使数据库层操作更加简单,方便解耦
1.2springdatajpa的特性
SpringData Jpa 极大简化了数据库访问层代码。 如何简化的呢? 使用了SpringDataJpa,我们的dao层中只需要写接口,就自动具有了增删改查、分页查询等方法。
1.3Spring Data JPA 与 JPA和hibernate之间的关系
JPA是一套规范,内部是有接口和抽象类组成的。hibernate是一套成熟的ORM框架,而且Hibernate实现了JPA规范,所以也可以称hibernate为JPA的一种实现方式,我们使用JPA的API编程,意味着站在更高的角度上看待问题(面向接口编程)
Spring Data JPA是Spring提供的一套对JPA操作更加高级的封装,是在JPA规范下的专门用来进行数据持久化的解决方案。
2.SpringDataJpa快速入门
2.1需求说明
Spring Data JPA完成客户的基本CRUD操作
2.2搭建环境
项目依赖
<!--hibernate-->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.4.15.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.0.7.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.3.6.Final</version>
</dependency>
<!--springdatajpa-->
<dependency>
<groupId>javax.el</groupId>
<artifactId>javax.el-api</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>javax.el</artifactId>
<version>2.2.4</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.3.0.RELEASE</version>
</dependency>
Spring整合SpringDataJPA
<?xml version="1.0" encoding="UTF-8"?>
<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">
<!--组件扫描-->
<context:component-scan base-package="com.example">
<!-- 配置要忽略的注解 -->
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--Spring整合MyBatis-->
<!--配置数据源-->
<!--引入外部属性文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="url" value="${jdbc.url}"/>
<property name="driverClassName" value="${jdbc.DriverName}"/>
</bean>
<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<jpa:repositories base-package="com.example.mapper"
transaction-manager-ref="transactionManager"
entity-manager-factory-ref="entityManagerFactory"></jpa:repositories>
<!--事务管理-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="insert*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!--配置切入点表达式-->
<aop:pointcut id="my" expression="execution(* com.example.service.impl.*.* (..))"></aop:pointcut>
<aop:advisor advice-ref="txAdvice" pointcut-ref="my"></aop:advisor>
</aop:config>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"></property>
<property name="packagesToScan" value="com.example.domain"></property>
<property name="persistenceProvider">
<bean class="org.hibernate.jpa.HibernatePersistenceProvider"/>
</property>
<!--jpa的供应商适配器-->
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="generateDdl" value="false"></property>
<property name="database" value="MYSQL"></property>
<property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"></property>
<property name="showSql" value="true"></property>
</bean>
</property>
<property name="jpaDialect">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"></bean>
</property>
</bean>
</beans>
关联映射实体类
@Entity
@Table(name = "t_person")
public class Person {
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "p_name")
private String name;
@Column(name = "p_age")
private Integer age;
@Column(name = "p_email")
private String email;
@Column(name = "p_birth")
private Date birth;
}
mapper接口
/**
* @author yinhuidong
* @createTime 2020-06-04-15:46
* JpaRepository<Person, Long> 用来完成基本CRUD
* JpaSpecificationExecutor<Person> 用来完成复杂查询
*/
public interface PersonMapper extends JpaRepository<Person, Long>, JpaSpecificationExecutor<Person> {
}
测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class TestCRUD {
@Autowired
private PersonMapper mapper;
//保存操作
@Test
public void testSave(){
mapper.save(new Person("aaa",20,"aaa@qq.com",new Date()));
}
/**
* 修改操作:
* 修改与保存共用一个方法save
* 当传入的实体类有主键就是修改,没有主键就是保存
*/
@Test
public void testUpdate(){
Person person = new Person("aaa",20,"bbb@qq.com",new Date());
person.setId(8l);
mapper.save(person);
}
//删除
@Test
public void testDelete(){
Person person = new Person("aaa",20,"bbb@qq.com",new Date());
person.setId(8l);
mapper.delete(person);
}
//根据ID查询
@Test
public void testFindById(){
System.out.println(mapper.findById(7l).get());
}
//查询所有
@Test
public void testFindAll(){
mapper.findAll().forEach(System.out::println);
}
}
3.SpringDataJPA的底层代码分析
3.1常用接口分析
在自定义mapper接口中,并没有提供任何方法就可以使用其中的方法,那么这些方法究竟是怎么来的?,由于我们的接口继承了JpaRepository
和JpaSpecificationExecutor
,所以我们可以使用这两个接口的所有方法。
在使用Spring Data JPA
时,一般实现JpaRepository
和JpaSpecificationExecutor
接口,这样就可以使用这些接口中定义的方法,但是这些方法都只是一些声明,没有具体的实现方式,那么在 Spring Data JPA
中它又是怎么实现的呢?
3.2SpringDataJPA的实现过程
通过刚才的入门案例,打断点来分析SpringDataJPA的执行过程。
以findById()为例。
断点执行到方法上时,我们可以发现注入的是personmapper对象,本质上是通过jdk的动态代理生成的一个对象。
查看具体过程:
当程序执行的时候,会通过JdkDynamicAopProxy
的invoke
方法,对Dao
对象生成动态代理对象。根据对Spring Data JPA
介绍而知,要想进行findOne
查询方法,最终还是会出现JPA规范的API完成操作,那么这些底层代码存在于何处呢?答案很简单,都隐藏在通过JdkDynamicAopProxy
生成的动态代理对象当中,而这个动态代理对象就是SimpleJpaRepository
,通过 SimpleJpaRepository
的源码分析,定位到了findOne
方法,在此方法中,返回em.find()
的返回结果,那么em又是什么呢?
继续查找em对象,我们发现em
就是EntityManager
对象,而他是JPA
原生的实现方式,所以我们得到结论Spring Data JPA
只是对标准JPA操作进行了进一步封装,简化了Dao
层代码的开发。
由此可得:SpringDataJpa完整执行流程:
SpringDataJpa–>JPA–>hibernate–>数据库
4.SpringDataJPA的查询方式
4.1使用jpql的方式查询
使用Spring Data JPA提供的查询方法已经可以解决大部分的应用场景,但是对于某些业务来说,我们还需要灵活的构造查询条件,这时就可以使用@Query注解,结合JPQL的语句方式完成查询
@Query 注解的使用非常简单,只需在方法上面标注该注解,同时提供一个JPQL查询语句即可
/**
* 测试使用JPQL的方式进行查询
*/
@Query("from Person ")
List<Person> MyfindAll();
@Query("from Person where id=?1")
Person MyfindById(Long id);
@Query(value = "update Person set name=?1 where id=?2")
@Modifying
@Transactional
void MyUpdate(String name,Long id);
4.2使用SQL语句查询
/**
* nativeQuery : 使用本地sql的方式查询
*/
@Query(value = "select * from t_person",nativeQuery = true)
List<Person> MyfindAll();
@Query(value = "select * from t_person where id=?1",nativeQuery = true)
Person MyfindById(Long id);
@Query(value = "update t_person set p_name=?1 where id=?2",nativeQuery = true)
@Modifying
@Transactional
void MyUpdate(String name,Long id);
4.3方法命名规则查询
方法命名规则查询就是根据方法的名字,就能创建查询。只需要按照Spring Data JPA提供的方法命名规则定义方法的名称,就可以完成查询工作。Spring Data JPA在程序执行的时候会根据方法名称进行解析,并自动生成查询语句进行查询
按照Spring Data JPA 定义的规则,查询方法以findBy开头,涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性首字母需大写。框架在进行方法名解析时,会先把方法名多余的前缀截取掉,然后对剩下部分进行解析。
Keyword | Sample | JPQL |
---|---|---|
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstnameIs, findByFirstnameEquals | … where x.firstname = ?1 |
Between | findByStartDateBetween | … where x.startDate between ?1 and ?2 |
LessThan | findByAgeLessThan | … where x.age < ?1 |
LessThanEqual | findByAgeLessThanEqual | … where x.age ⇐ ?1 |
GreaterThan | findByAgeGreaterThan | … where x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … where x.age >= ?1 |
After | findByStartDateAfter | … where x.startDate > ?1 |
Before | findByStartDateBefore | … where x.startDate < ?1 |
IsNull | findByAgeIsNull | … where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull | … where x.age not null |
Like | findByFirstnameLike | … where x.firstname like ?1 |
NotLike | findByFirstnameNotLike | … where x.firstname not like ?1 |
StartingWith | findByFirstnameStartingWith | … where x.firstname like ?1 (parameter bound with appended %) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1 (parameter bound with prepended %) |
Containing | findByFirstnameContaining | … where x.firstname like ?1 (parameter bound wrapped in %) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection age) | … where x.age not in ?1 |
TRUE | findByActiveTrue() | … where x.active = true |
FALSE | findByActiveFalse() | … where x.active = false |
IgnoreCase | findByFirstnameIgnoreCase | … where UPPER(x.firstame) = UPPER(?1) |
三,springdataJpa的动态查询与多表查询
1.动态查询
有时我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,在Spring Data JPA中可以通过JpaSpecificationExecutor接口查询。相比JPQL,其优势是类型安全,更加的面向对象。
import java.util.List;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
/**
* JpaSpecificationExecutor中定义的方法
**/
public interface JpaSpecificationExecutor<T> {
//根据条件查询一个对象
T findOne(Specification<T> spec);
//根据条件查询集合
List<T> findAll(Specification<T> spec);
//根据条件分页查询
Page<T> findAll(Specification<T> spec, Pageable pageable);
//排序查询查询
List<T> findAll(Specification<T> spec, Sort sort);
//统计查询
long count(Specification<T> spec);
}
对于JpaSpecificationExecutor,这个接口基本是围绕着Specification接口来定义的。我们可以简单的理解为,Specification构造的就是查询条件。
Specification接口中只定义了如下一个方法:
//构造查询条件
/**
* root :Root接口,代表查询的根对象,可以通过root获取实体中的属性
* query :代表一个顶层查询对象,用来自定义查询
* cb :用来构建查询,此对象里有很多条件方法
**/
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
1.1 使用Specifications完成条件查询
//动态条件查询
@Test
public void test7(){
//使用匿名了内部类的方式,创建一个Specification的实现类,并实现toPredicate()
Specification<Person> sp=new Specification<Person>() {
@Override
public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
//criteriaBuilder:构建查询,添加查询方式 like:模糊匹配
return criteriaBuilder.like(root.get("name").as(String.class),"少妇白洁");
}
};
mapper.findAll(sp).forEach(System.out::println);
}
1.2基于Specification的分页查询
//动态分页查询
@Test
public void test8(){
//构建查询条件
Specification<Person> sp=new Specification<Person>() {
@Override
public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
return criteriaBuilder.like(root.get("name").as(String.class),"少妇%");
}
};
/**
* 构建分页参数:
* Pageable:接口
* PageRequet实现了Pageable接口,调用of方法的形式构造。
* 第一个参数:页码(从0开始)
* 第二个参数:每页查询条数
*/
Pageable request = PageRequest.of(0,1);
/**
* 分页查询:封装为SpringDataJpa内部的pageBean
* 此重载findAlll方法为分页方法需要两个参数
* 第一个参数、;查询条件
* 第二个参数:分页参数
*/
Page<Person> page = mapper.findAll(sp, request);
page.forEach(System.out::println);
}
1.3方法对应关系
方法名称 | Sql对应关系 |
---|---|
equle | filed = value |
gt(greaterThan ) | filed > value |
lt(lessThan ) | filed < value |
ge(greaterThanOrEqualTo ) | filed >= value |
le( lessThanOrEqualTo) | filed <= value |
notEqule | filed != value |
like | filed like value |
notLike | filed not like value |
2.多表设计
2.1表之间关系的划分
数据库中多表之间存在着三种关系,如图所示。
从图可以看出,系统设计的三种实体关系分别为:多对多、一对多和一对一关系。注意:一对多关系可以看为两种: 即一对多,多对一。所以说四种更精确。
2.2 在JPA框架中表关系的分析步骤
在实际开发中,我们数据库的表难免会有相互的关联关系,在操作表的时候就有可能会涉及到多张表的操作。而在这种实现了ORM思想的框架中(如JPA),可以让我们通过操作实体类就实现对数据库表的操作。所以学习重点是:掌握配置实体之间的关联关系。
第一步:首先确定两张表之间的关系。
如果关系确定错了,后面做的所有操作就都不可能正确。
第二步:在数据库中实现两张表的关系
第三步:在实体类中描述出两个实体的关系
第四步:配置出实体类和数据库表的关系映射(重点)
3.jpa中的一对多
3.1情景模拟
上个案例我创建的是一张Person表,此时在创建一张Dog表,每个人都可以对应着多个宠物狗。
此时从人的角度就是一对多的关系。
3.2表关系建立
在一对多关系中,我们习惯把一的一方称之为主表,把多的一方称之为从表。在数据库中建立一对多的关系,需要使用数据库的外键约束。
什么是外键?
指的是从表中有一列,取值参照主表的主键,这一列就是外键。
一对多数据库关系的建立,如下图所示
3.3 实体类关系建立以及映射配置
数据库建表语句
CREATE TABLE t_person(
id INT PRIMARY KEY AUTO_INCREMENT,
p_age INT ,
p_birth DATE,
p_email VARCHAR(20),
p_name VARCHAR(20)
);
CREATE TABLE t_dog(
d_id INT PRIMARY KEY AUTO_INCREMENT,
d_name VARCHAR(20),
p_id INT REFERENCES t_person(id)
);
实体类关系映射
@Entity
@Table(name = "t_person")
public class Person {
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "p_name")
private String name;
@Column(name = "p_age")
private Integer age;
@Column(name = "p_email")
private String email;
@Column(name = "p_birth")
private Date birth;
@OneToMany(mappedBy="person")
private List<Dog> dogs=new ArrayList<>();
}
@Entity
@Table(name = "t_dog")
public class Dog {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "d_id")
private Long id;
@Column(name = "d_name")
private String name;
@ManyToOne(targetEntity = Person.class)
@JoinColumn(name = "p_id",referencedColumnName = "id")
private Person person;
}
3.4映射的注解说明
@OneToMany:
作用:建立一对多的关系映射
属性:
targetEntityClass:指定多的多方的类的字节码
mappedBy:指定从表实体类中引用主表对象的名称。
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
orphanRemoval:是否使用孤儿删除
@ManyToOne
作用:建立多对一的关系
属性:
targetEntityClass:指定一的一方实体类字节码
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
optional:关联是否可选。如果设置为false,则必须始终存在非空关系。
@JoinColumn
作用:用于定义主键字段和外键字段的对应关系。
属性:
name:指定外键字段的名称
referencedColumnName:指定引用主表的主键字段名称
unique:是否唯一。默认值不唯一
nullable:是否允许为空。默认值允许。
insertable:是否允许插入。默认值允许。
updatable:是否允许更新。默认值允许。
columnDefinition:列的定义信息。
3.5一对多的操作
1)添加
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class OneToManyTest {
@Autowired
private PersonMapper personMapper;
@Autowired
private DogMapper dogMapper;
/**
* 测试保存:
* 同时保存一个狗主人和一只狗的信息
*/
@Test
@Transactional
@Rollback(value = false)
public void testSave(){
Person person = new Person("张敏",26,"baijie@qq.com",new Date());
Dog dog = new Dog();
dog.setName("金毛");
person.getDogs().add(dog);
dog.setPerson(person);
personMapper.save(person);
dogMapper.save(dog);
}
}
2)删除
/**
* 删除操作
*/
@Test
public void testDelete(){
//删除主表数据,可以直接删除,对应从表外键字段还是删除的主表ID
personMapper.deleteById(4l);
//此时在删除从表会报错
dogMapper.deleteById(4l);
//如果先删除从表再删除主表则没有问题
}
3)级联操作
级联操作:指操作一个对象同时操作它的关联对象
使用方法:只需要在操作主体的注解上配置cascade
@OneToMany(mappedBy="person",cascade = CascadeType.ALL)
cascade:配置级联操作
CascadeType.MERGE
级联更新
CascadeType.PERSIST
级联保存:
CascadeType.REFRESH
级联刷新:
CascadeType.REMOVE
级联删除:
CascadeType.ALL
包含所有
4.jpa中的多对多
4.1.情景模拟
用户和角色:一个用户可以有多个角色,一个角色可以对应多个用户
4.2表关系建立
CREATE TABLE t_role(
r_id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20)
);
CREATE TABLE t_person_role(
id INT PRIMARY KEY AUTO_INCREMENT,
r_id INT REFERENCES t_role(r_id),
p_id INT REFERENCES t_person(id)
)
4.3实体关系建立以及映射配置
@Entity
@Table(name = "t_person")
public class Person {
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "p_name")
private String name;
@Column(name = "p_age")
private Integer age;
@Column(name = "p_email")
private String email;
@Column(name = "p_birth")
private Date birth;
@OneToMany(mappedBy="person",cascade = CascadeType.ALL)
private List<Dog> dogs=new ArrayList<>();
//多对多关系映射
@ManyToMany(mappedBy ="persons")
private List<Role> roles=new ArrayList<>();
}
@Entity
@Table(name = "t_role")
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "r_id")
private Integer id;
@Column(name = "name")
private String name;
@ManyToMany
@JoinTable(name="t_person_role",//中间表的名称
joinColumns={@JoinColumn(name="r_id",referencedColumnName="r_id")},
inverseJoinColumns={@JoinColumn(name="p_id",referencedColumnName="id")}
)
private List<Person> persons=new ArrayList<>();
}
4.4映射的注解说明
@ManyToMany
作用:用于映射多对多关系
属性:
cascade:配置级联操作。
fetch:配置是否采用延迟加载。
targetEntity:配置目标的实体类。映射多对多的时候不用写。
@JoinTable
作用:针对中间表的配置
属性:
nam:配置中间表的名称
joinColumns:中间表的外键字段关联当前实体类所对应表的主键字段
inverseJoinColumn:中间表的外键字段关联对方表的主键字段
@JoinColumn
作用:用于定义主键字段和外键字段的对应关系。
属性:
name:指定外键字段的名称
referencedColumnName:指定引用主表的主键字段名称
unique:是否唯一。默认值不唯一
nullable:是否允许为空。默认值允许。
insertable:是否允许插入。默认值允许。
updatable:是否允许更新。默认值允许。
columnDefinition:列的定义信息。
4.5多对多的操作
/**
* @author yinhuidong
* @createTime 2020-06-04-21:19
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class ManyToManyTest {
@Autowired
private PersonMapper personMapper;
@Autowired
private RoleMapper roleMapper;
/**
* 保存
*/
@Test
@Transactional
@Rollback(false)
public void test1(){
Person person = new Person();
person.setName("尹会东");
Person person2 = new Person();
person2.setName("张贝贝");
Role r1 = new Role();
r1.setName("学生");
Role r2 = new Role();
r2.setName("丈夫");
person.getRoles().add(r1);
person.getRoles().add(r2);
person2.getRoles().add(r1);
person2.getRoles().add(r2);
r1.getPersons().add(person);
r2.getPersons().add(person);
r1.getPersons().add(person2);
r2.getPersons().add(person2);
personMapper.save(person);
personMapper.save(person2);
roleMapper.save(r1);
roleMapper.save(r2);
}
@Test
@Transactional
@Rollback(false)
public void test2(){
personMapper.deleteById(10l);
}
}
5.SpringDataJPA的多表查询
对象导航图查询
对象图导航检索方式是根据已经加载的对象,导航到他的关联对象。它利用类与类之间的关系来检索对象。例如:我们通过ID查询方式查出一个客户,可以调用Customer类中的getLinkMans()方法来获取该客户的所有联系人。对象导航查询的使用要求是:两个对象之间必须存在关联关系。
查询一个Person获取它对应的所有Role
@Transactional
@Test
public void test3(){
Person person = personMapper.findById(11l).get();
System.out.println("person = " + person);
person.getRoles().forEach(System.out::println);
}
查询一个角色,获取角色对应的所有person
@Transactional
@Test
public void test3(){
Role role = roleMapper.findById(5l).get();
System.out.println("role = " + role);
role.getPersons().forEach(System.out::println);
}
对象导航查询的问题分析
问题1:我们查询客户时,要不要把联系人查询出来?
分析:如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。如果我们查出来的,不使用时又会白白的浪费了服务器内存。
解决:采用延迟加载的思想。通过配置的方式来设定当我们在需要使用时,发起真正的查询。
配置方式:
//LAZY
@ManyToMany(mappedBy ="persons",fetch = FetchType.EAGER)
FetchType.EAGER
立即加载
FetchType.LAZY
懒加载
r1.getPersons().add(person);
r2.getPersons().add(person);
r1.getPersons().add(person2);
r2.getPersons().add(person2);
personMapper.save(person);
personMapper.save(person2);
roleMapper.save(r1);
roleMapper.save(r2);
}
@Test
@Transactional
@Rollback(false)
public void test2(){
personMapper.deleteById(10l);
}
}
## 5.SpringDataJPA的多表查询
### 5.1对象导航图查询
对象图导航检索方式是根据已经加载的对象,导航到他的关联对象。它利用类与类之间的关系来检索对象。例如:我们通过ID查询方式查出一个客户,可以调用Customer类中的getLinkMans()方法来获取该客户的所有联系人。对象导航查询的使用要求是:两个对象之间必须存在关联关系。
**查询一个Person获取它对应的所有Role**
```java
@Transactional
@Test
public void test3(){
Person person = personMapper.findById(11l).get();
System.out.println("person = " + person);
person.getRoles().forEach(System.out::println);
}
查询一个角色,获取角色对应的所有person
@Transactional
@Test
public void test3(){
Role role = roleMapper.findById(5l).get();
System.out.println("role = " + role);
role.getPersons().forEach(System.out::println);
}
对象导航查询的问题分析
问题1:我们查询客户时,要不要把联系人查询出来?
分析:如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。如果我们查出来的,不使用时又会白白的浪费了服务器内存。
解决:采用延迟加载的思想。通过配置的方式来设定当我们在需要使用时,发起真正的查询。
配置方式:
//LAZY
@ManyToMany(mappedBy ="persons",fetch = FetchType.EAGER)
FetchType.EAGER
立即加载
FetchType.LAZY
懒加载