JPA基础知识
传统jdbc存在的问题?
- 操作繁琐
解决方案: 封装工具类- 占位符赋值麻烦
解决方案:
*思路: 我么能不能通过面向对象的方法进行封装呢?达到封装好之后sava(Object obj) 的方法
*思路延伸: 我们能不能自动拼接sql呢?只要完成自动拼接,我们就实现了上面的想法
*通过分析:我们发现,只要能
- 建立实体类之间的对应关系
- 简历实体类属性和表字段之间的关系
这样我们就能够拼接处sql达到封装的目的- 上述简历实体类对象和数据库表之间的映射关系,我们就叫作对象-关系-映射(Object Relational Mapping) 简称 ORM
ORM思想的概述
- 建立两个映射关系:
- 实体类和表的映射关系
- 实体类中属性和表中字段的映射关系
- 不再重点关注:sql语句
- 主要目的:操作实体类就相当于操作数据库表
- orm思想: 一个框架,一个想法
- orm框架: 实现了这种思想的框架
- Mybatis
- Hibernate
jpa介绍
JPA接口也一样,后续我们更换框架也不需要变更代码,只需要更换底层配置即可 我们用Java定义的一套接口开发.当我们框架使用变更的时候就不用变更底层代码了
- JPA 是一套规范,它不干活,干活的是实现了JPA这一套接口的框架
- 我们掌握了JPA 的使用就相当于掌握了所有实现JPA的框架的使用
优点:
- 简单方便
- 标准
入门案例
创建步骤
- 导入jar (Manven 到如配置文件即可) 2) 编写JPA 的核心配置文件 3) 编写客户的实体类 4) 配置映射关系
- 保存客户信息到数据库中
-
配置文件 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> <!-- 数据库配置 用户名:javax.persistence.jdbc.user 密码:javax.persistence.jdbc.password url:javax.persistence.jdbc.url 数据库驱动:javax.persistence.jdbc.driver --> <properties> <!-- 数据库路径 --> <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="123"/> <!-- 数据库驱动 --> <property name="javax.persistence.jdbc.driver" value="com.mysql.jdbc.Driver"/> <!--配置jpa实现方(hibernate)的配置信息 显示sql : false|true 自动创建数据库表 : hibernate.hbm2ddl.auto create : 程序运行时创建数据库表(如果有表,先删除表再创建) update :程序运行时创建表(如果有表,不会创建表) none :不会创建表 --> <property name="hibernate.show_sql" value="true" /> <property name="hibernate.format_sql" value="true" /> <property name="hibernate.hbm2ddl.auto" value="update" /> </properties> </persistence-unit> </persistence>
-
实体类
package cn.itcast.domain; import javax.persistence.*; /** 客户的实体类 配置映射关系 1.实体类和表的映射关系 @Entity:声明实体类 @Table : 配置实体类和表的映射关系 name : 配置数据库表的名称 2.实体类中属性和表中字段的映射关系 */ @Entity @Table(name = "cst_customer") public class Customer { /** * @Id:声明主键的配置 * @GeneratedValue:配置主键的生成策略 * strategy * GenerationType.IDENTITY :自增,mysql * * 底层数据库必须支持自动增长(底层数据库支持的自动增长方式,对id自增) * GenerationType.SEQUENCE : 序列,oracle * * 底层数据库必须支持序列 * GenerationType.TABLE : jpa提供的一种机制,通过一张数据库表的形式帮助我们完成主键自增 * GenerationType.AUTO : 由程序自动的帮助我们选择主键生成策略 * @Column:配置属性和字段的映射关系 * name:数据库表中字段的名称 */ @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "cust_id") private Long custId; //客户的主键 @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;//客户地址 public Long getCustId() { return custId; } public void setCustId(Long custId) { this.custId = custId; } public String getCustName() { return custName; } public void setCustName(String custName) { this.custName = custName; } public String getCustSource() { return custSource; } public void setCustSource(String custSource) { this.custSource = custSource; } public String getCustLevel() { return custLevel; } public void setCustLevel(String custLevel) { this.custLevel = custLevel; } public String getCustIndustry() { return custIndustry; } public void setCustIndustry(String custIndustry) { this.custIndustry = custIndustry; } public String getCustPhone() { return custPhone; } public void setCustPhone(String custPhone) { this.custPhone = custPhone; } public String getCustAddress() { return custAddress; } public void setCustAddress(String custAddress) { this.custAddress = custAddress; } @Override public String toString() { return "Customer{" + "custId=" + custId + ", custName='" + custName + '\'' + ", custSource='" + custSource + '\'' + ", custLevel='" + custLevel + '\'' + ", custIndustry='" + custIndustry + '\'' + ", custPhone='" + custPhone + '\'' + ", custAddress='" + custAddress + '\'' + '}'; }}
- 测试
package cn.itcast.test; import cn.itcast.domain.Customer; import cn.itcast.utils.JpaUtils; import org.junit.jupiter.api.Test; import javax.persistence.EntityManager; import javax.persistence.EntityTransaction; public class JpaTest { /** * 测试jpa的保存 * 案例:保存一个用户到数据库中 * Jpa的操作步骤: * 1,加载配置文件创建工厂(实体类管理器工厂)对象 * 2,通过实体类管理器工厂获取实体类管理器 * 3,获取事务对象,开启事务 * 4,完成增删改查操作 * 5,提交或回滚事物 * 6,释放资源 */ @Test public void testSave(){ //1,加载配置文件创建工厂(实体类管理器工厂)对象 //EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa"); // 2,通过实体类管理器工厂获取实体类管理器 // EntityManager em = factory.createEntityManager(); EntityManager em= JpaUtils.getEntityManager(); // 3,获取事务对象,开启事务 EntityTransaction tx = em.getTransaction();//获取事务 tx.begin();// 开启事务 // 4,完成增删改查操作 Customer customer=new Customer(); customer.setCustName("杜金龙"); customer.setCustPhone("13598322004"); customer.setCustAddress("河南郑州"); em.persist(customer); // 5,提交或回滚事物 tx.commit(); // 6,释放资源 em.close(); } @Test public void testFind(){ // 1,通过工具类获取EntityManager对象 EntityManager em= JpaUtils.getEntityManager(); // 3,获取事务对象,开启事务 EntityTransaction tx = em.getTransaction();//获取事务 tx.begin();// 开启事务 // 4,完成增删改查操作 /** * find:根据id查询数据 * class:要查询数据的结果要包装的实体类类型的字节码 * id:查询的主键的取值 */ Customer customer = em.find(Customer.class, 1l); System.out.println(customer); // 5,提交或回滚事物 tx.commit(); // 6,释放资源 em.close(); } /** * 根据id查询客户 * getReference方法 * 1.获取的对象是一个动态代理对象 * 2.调用getReference方法不会立即发送sql语句查询数据库 * * 当调用查询结果对象的时候,才会发送查询的sql语句:什么时候用,什么时候发送sql语句查询数据库 * * 延迟加载(懒加载) * * 得到的是一个动态代理对象 * * 什么时候用,什么使用才会查询 */ @Test public void testReference(){ // 1,通过工具类获取EntityManager对象 EntityManager em= JpaUtils.getEntityManager(); // 3,获取事务对象,开启事务 EntityTransaction tx = em.getTransaction();//获取事务 tx.begin();// 开启事务 // 4,完成增删改查操作 /** * getReference:根据id查询数据 * class:要查询数据的结果要包装的实体类类型的字节码 * id:查询的主键的取值 */ Customer customer = em.getReference(Customer.class, 2l); System.out.println(customer); // 5,提交或回滚事物 tx.commit(); // 6,释放资源 em.close(); } /** * remove 删除操作 */ @Test public void testRemove(){ // 1,通过工具类获取EntityManager对象 EntityManager em= JpaUtils.getEntityManager(); // 3,获取事务对象,开启事务 EntityTransaction tx = em.getTransaction();//获取事务 tx.begin();// 开启事务 // 4,完成增删改查操作 //i 根据id查询客户 Customer customer= em.find(Customer.class, 1l); //ii 调用remove方法进行删除操作 em.remove(customer); // 5,提交或回滚事物 tx.commit(); // 6,释放资源 em.close(); } /** * merge 更新操作 */ @Test public void testMerge(){ // 1,通过工具类获取EntityManager对象 EntityManager em= JpaUtils.getEntityManager(); // 3,获取事务对象,开启事务 EntityTransaction tx = em.getTransaction();//获取事务 tx.begin();// 开启事务 // 4,完成增删改查操作 //i 根据id查询客户 Customer customer= em.find(Customer.class, 2l); customer.setCustName("李二狗"); //ii 调用merge方法进行更新操作 em.merge(customer); // 5,提交或回滚事物 tx.commit(); // 6,释放资源 em.close(); } }
API讲解
加载配置文件创建实体管理器工厂
Persisitence:静态方法(根据持久化单元名称创建实体管理器工厂)
createEntityMnagerFactory(持久化单元名称)
作用:创建实体管理器工厂
2.根据实体管理器工厂,创建实体管理器
EntityManagerFactory :获取EntityManager对象
方法:createEntityManager
* 内部维护的很多的内容
内部维护了数据库信息,
维护了缓存信息
维护了所有的实体管理器对象
再创建EntityManagerFactory的过程中会根据配置创建数据库表
* EntityManagerFactory的创建过程比较浪费资源
特点:线程安全的对象
多个线程访问同一个EntityManagerFactory不会有线程安全问题
* 如何解决EntityManagerFactory的创建过程浪费资源(耗时)的问题?
思路:创建一个公共的EntityManagerFactory的对象
* 静态代码块的形式创建EntityManagerFactory
3.创建事务对象,开启事务
EntityManager对象:实体类管理器
getTransaction : 创建事务对象
presist : 保存
merge : 更新
remove : 删除
find/getRefrence : 根据id查询
Transaction 对象 : 事务
begin:开启事务
commit:提交事务
rollback:回滚
4.增删改查操作
5.提交事务
6.释放资源
抽取jpaUtils工具类
package cn.itcast.utils;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
/**
* 解决时期管理器工厂浪费资源和耗时问题
* 通过静态代码块的形式,当程序第一次访问此工具类的时候,创建一个共有的实体管理器工厂对象
* 第一次访问getEntityManager方法:经过静态代码块创建一个factory对象,在调用方法创建一个entityManager对象.
* 第二次访问的时候,fatory已经存在,直接通过已经创建好的fatory对象创建一个entityManager对象.
*/
public class JpaUtils {
private static EntityManagerFactory factory;
static{
//加载配置文件,创建一个entityManagerFactory工厂对象
factory= Persistence.createEntityManagerFactory("myJpa");
}
public static EntityManager getEntityManager(){
//通过过工厂对象获取实体管理器对象
return factory.createEntityManager();
}
}
jpql的介绍
package cn.itcast.test;
import cn.itcast.utils.JpaUtils;
import org.junit.jupiter.api.Test;
import javax.persistence.EntityManager;
import javax.persistence.EntityTransaction;
import javax.persistence.Query;
import java.util.List;
/**
* 测试jqpl查询
*/
public class JpqlTest {
/**
* 查询全部
*/
@Test
public void testFindAll(){
// 1,通过工具类获取EntityManager对象
EntityManager em= JpaUtils.getEntityManager();
// 3,获取事务对象,开启事务
EntityTransaction tx = em.getTransaction();//获取事务
tx.begin();// 开启事务
// 4,完成增删改查操作
/**
* 查询全部
* jqpl:from cn.itcast.domain.Customer
* sql:SELECT * FROM cst_customer
*/
//创建jpql语句
String jpql="from Customer ";
//获取query查询对象
Query query = em.createQuery(jpql);
//发送查询 并封装结果集
List list = query.getResultList();
for(Object obj:list){
System.out.println(obj);
}
// 5,提交或回滚事物
tx.commit();
// 6,释放资源
em.close();
}
/**
* 排序查询: 倒序查询全部客户(根据id倒序)
* sql:SELECT * FROM cst_customer ORDER BY cust_id DESC
* jpql:from Customer order by custId desc
*
* 进行jpql查询
* 1.创建query查询对象
* 2.对参数进行赋值
* 3.查询,并得到返回结果
*/
@Test
public void testOrders(){
// 1,通过工具类获取EntityManager对象
EntityManager em= JpaUtils.getEntityManager();
// 3,获取事务对象,开启事务
EntityTransaction tx = em.getTransaction();//获取事务
tx.begin();// 开启事务
// 4,完成增删改查操作
//创建query查询对象
String jpql="from Customer order by custId desc";
Query query = em.createQuery(jpql);
//对参数进行赋值
//查询,并得到返回结果
List list = query.getResultList();
for(Object obj:list){
System.out.println(obj);
}
// 5,提交或回滚事物
tx.commit();
// 6,释放资源
em.close();
}
/**
* 使用jpql查询,统计客户的总数
* sql:SELECT COUNT(cust_id) FROM cst_customer
* jpql:select count(custId) from Customer
*/
@Test
public void testCount(){
// 1,通过工具类获取EntityManager对象
EntityManager em= JpaUtils.getEntityManager();
// 3,获取事务对象,开启事务
EntityTransaction tx = em.getTransaction();//获取事务
tx.begin();// 开启事务
// 4,完成增删改查操作
//创建query查询对象
String jpql="select count(custId) from Customer";
Query query = em.createQuery(jpql);
//对参数进行赋值
//查询,并得到返回结果
/**
* getResultList : 直接将查询结果封装为list集合
* getSingleResult : 得到唯一的结果集
*/
Object o = query.getSingleResult();
System.out.println(o);
// 5,提交或回滚事物
tx.commit();
// 6,释放资源
em.close();
}
/**
* 分页查询
* sql:select * from cst_customer limit 0,2
* jqpl : from Customer
*/
@Test
public void testPage(){
// 1,通过工具类获取EntityManager对象
EntityManager em= JpaUtils.getEntityManager();
// 3,获取事务对象,开启事务
EntityTransaction tx = em.getTransaction();//获取事务
tx.begin();// 开启事务
// 4,完成增删改查操作
//创建query查询对象
String jpql="from Customer";
Query query = em.createQuery(jpql);
//对参数进行赋值 --分页参数
//1,起始索引
query.setFirstResult(0);
//2,每页查询条数
query.setMaxResults(2);
//查询,并得到返回结果
/**
* getResultList : 直接将查询结果封装为list集合
* getSingleResult : 得到唯一的结果集
*/
List list = query.getResultList();
for(Object o:list){
System.out.println(o);
}
// 5,提交或回滚事物
tx.commit();
// 6,释放资源
em.close();
}
/**
* 条件查询
* 案例:查询客户名称以‘杜’开头的客户
* sql:SELECT * FROM cst_customer WHERE cust_name LIKE ?
* jpql : from Customer where custName like ?
*/
@Test
public void testCondition(){
// 1,通过工具类获取EntityManager对象
EntityManager em= JpaUtils.getEntityManager();
// 3,获取事务对象,开启事务
EntityTransaction tx = em.getTransaction();//获取事务
tx.begin();// 开启事务
// 4,完成增删改查操作
//创建query查询对象
String jpql="from Customer where custName like ?";
Query query = em.createQuery(jpql);
//对参数进行赋值
query.setParameter(1,"杜%");
//查询,并得到返回结果
/**
* getResultList : 直接将查询结果封装为list集合
* getSingleResult : 得到唯一的结果集
*/
List list = query.getResultList();
for(Object o:list){
System.out.println(o);
}
// 5,提交或回滚事物
tx.commit();
// 6,释放资源
em.close();
}
}
SpringDataJpa
Spring-data-jpa的基本介绍:JPA诞生的缘由是为了整合第三方ORM框架,建立一种标准的方式,百度百科说是JDK为了实现ORM的天下归一,目前也是在按照这个方向发展,但是还没能完全实现。在ORM框架中,Hibernate是一支很大的部队,使用很广泛,也很方便,能力也很强,同时Hibernate也是和JPA整合的比较良好,我们可以认为JPA是标准,事实上也是,JPA几乎都是接口,实现都是Hibernate在做,宏观上面看,在JPA的统一之下Hibernate很良好的运行。
入门案例
- pom.xml maven坐标导入
spring核心
spring AOP
spring IOC
spring ORM
springDataJPA
hibernate
mysql驱动
log4j日志
配置文件 META-INF/persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!-- 配置的扫描的包(实体类所在的包) -->
<property name="packagesToScan" value="cn.itcast.domain"/>
<!-- jpa的实现厂家 -->
<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 name="database" value="MYSQL"/>
<!-- 数据库方言,支持的特有的语法 -->
<property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"/>
<!-- 是否显示sql -->
<property name="showSql" value="true"/>
</bean>
</property>
<!-- jpa的方言: 高级的特性 -->
<property name="jpaDialect">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>
</property>
</bean>
<!-- 2,创建数据库连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="root"></property>
<property name="password" value="123"></property>
<property name="jdbcUrl" value="jdbc:mysql:///jpa"></property>
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
</bean>
<!-- 3, 整合spring data jpa -->
<jpa:repositories base-package="cn.itcast.dao" transaction-manager-ref="transactionManager"
entity-manager-factory-ref="entityManagerFactory"/>
<!-- 4, 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"></property>
</bean>
<!-- 4.txAdvice -->
<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>
<!-- 5,aop -->
<aop:config>
<aop:pointcut id="pointcut" expression="execution(* cn.itcast.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut"/>
</aop:config>
<!-- 5, 声明式事务 -->
<!-- 6,配置包扫描 -->
<context:component-scan base-package="cn.itcast"></context:component-scan>
![在这里插入图片描述](https://img-blog.csdn.net/20180923140822382?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM5ODc2NjY2/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) ![在这里插入图片描述](https://img-blog.csdn.net/20180923140911197?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM5ODc2NjY2/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)
- SpringdataJPA 底层是创建了Dao 代理对象,
- SpringdataJPA 底层依旧是封装了EntityManager 对象进行数据库操作
内部执行过程
- 通过JdkDynamicAopProxy的invoke方法创建了一个动态代理对象
- SimpleJpaRepository当中封装了JPA的操作(借助JPA的api完成数据库的CRUD)
- 通过hibernate完成数据库操作(封装了jdbc)
jpql的查询方式
jpql : jpa query language (jpq查询语言)
特点:语法或关键字和sql语句类似
查询的是类和类中的属性
需要将JPQL语句配置到接口方法上
1.特有的查询:需要在dao接口上配置方法
2.在新添加的方法上,使用注解的形式配置jpql查询语句
3.注解 : @Query()
在测试的过程中,@Transactional,@Rollback(value = false)的作用,update save delete
/** * 测试jpql的更新操作 * * springDataJpa中使用jpql完成 更新和删除操作 * * 需要手动的添加事物的支持 * * 默认执行结束后,回滚事务 * @Rollback : 设置是否自动回滚 * false | true */ @Test @Transactional @Rollback(value = false) public void testUpdateById(){ customerDao.updateById(4l,"王八"); }
占位符问题
/**
* 案例:根据客户名称和客户id查询客户
* jpql: from Customer where custName = ? and custId = ?
*
* 对于多个占位符参数
* 赋值的时候,默认的情况下,占位符的位置需要和方法参数中的位置保持一致
*
* 可以指定占位符参数的位置
* ? 索引的方式,指定此占位的取值来源
*/
@Query(value = "from Customer where custName = ?2 and custId = ?1")
public Customer findCustNameAndId(Long id, String name);
dao层接口,@Modifying
@Query : 代表的是进行查询
* 声明此方法是用来进行更新操作
@Modifying
* 当前执行的是一个更新操作,
getOne() 方法
因为是延迟加载, 如果没有事务等我们使用对象的时候,数据可能已经发生了变化,为了保证数据的一致性,
我们需要一个事务
/**
* 根据id从数据库查询
* @Transactional:保证getOne正常运行
*findone:
* em.find() :立即加载
* getOne():
* em.getReference :延迟加载\
* *返回的是一个客户端的动态代理对象
* *什么时候用 什么时候查询
*/
@Test
@Transactional
public void testGetOne(){
Customer one = customerDao.getOne(4l);
System.out.println(one);
}
sql查询
/**
* 使用sql的形式查询:
* 查询全部的客户
* sql : select * from cst_customer;
* Query : 配置sql查询
* value : sql语句
* nativeQuery : 查询方式
* true : sql查询
* false:jpql查询
*
*/
@Query(value="select * from cst_customer ",nativeQuery = true)
public List<Object [] > findSql(String name);
上面的时候去一个Object[] ,如果要获取一个对象的话,我们不能用* ,要写上全部的字段名称
@Query(value="select cust_name,cust_id,cust_address,cust_industry,cust_level,cust_phone,cust_source
from cst_customer where cust_name like ?1",nativeQuery = true)
public List findSql(String name);
方法命名规则查询
方法命名规则查询 是对JPQL的进一步封装
/**
* 方法名的约定:
* findBy : 查询
* 对象中的属性名(首字母大写) : 查询的条件
* CustName
* * 默认情况 : 使用 等于的方式查询
* 特殊的查询方式
*
* findByCustName -- 根据客户名称查询
*
* 再springdataJpa的运行阶段
* 会根据方法名称进行解析 findBy from xxx(实体类)
* 属性名称 where custName =
*
* 1.findBy + 属性名称 (根据属性名称进行完成匹配的查询=)
* 2.findBy + 属性名称 + “查询方式(Like | isnull)”
* findByCustNameLike
* 3.多条件查询
* findBy + 属性名 + “查询方式” + “多条件的连接符(and|or)” + 属性名 + “查询方式”
*/
Specifications动态查询
有时我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,在Spring DataJPA中可以通过JpaSpecificationExecutor接口查询。相比JPQL,其优势是类型安全,更加的面向对象。
/**
* 自定义查询条件
* 1,实现Specification接口(提供泛型: 查询的对象类型)
* 2,实现toPredicate方法(构造查询条件)
* 3,需要借助方法参数中的两个参数(
* root: 获取需要查询对象的属性
* CriteriaBuilder: 构造查询条件的,内部封装了很多查询条件(模糊匹配,精准匹配)
* )
* 案例:完成根据客户名称的模糊匹配,返回客户列表
* 客户名称以'杜'开头
* equal: 直接得到path对象(属性),然后进行比较即可
* gt,lt,le,like : 得到path对象,根据path指定的比较的参数类型,再去进行比较
* 指定的参数类型: path.as(类的字节码对象)---custName.as(String.class)
*/
@Test
public void testSpec2(){
Specification<Customer> spec=new Specification<Customer>() {
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
//查询属性名
Path<Object> custName = root.get("custName");
//查询方式: 模糊匹配
Predicate like = cb.like(custName.as(String.class), "杜%");
return like;
}
};
/**
* 添加排序
* 创建排序对象,需要调用构造方法实例化sort对象
* 第一个参数: 排序的顺序: (倒序,升序)
* Sort.Direction.DESC: 倒序
* Sort.Direction.ASC: 升序
* 第二个参数: 排序的属性名称
*/
Sort sort=new Sort(Sort.Direction.DESC,"custId");
List<Customer> list = customerDao.findAll(spec, sort);
for (Customer customer : list) {
System.out.println(customer);
}
}
方法对应关系
多表设计
- 表关系
- 在JPA框架中表关系的分析步骤
-
首先确定两张表之间的关系。
-
在数据库中实现两张表的关系
-
在实体类中描述出两个实体的关系
-
配置出实体类和数据库表的关系映射(重点)
JPA中的一对多
@OneToMany:
作用:建立一对多的关系映射
属性:
targetEntityClass:指定多的多方的类的字节码
mappedBy:指定从表实体类中引用主表对象的名称。
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
orphanRemoval:是否使用孤儿删除
@ManyToOne
作用:建立多对一的关系
属性:
targetEntityClass:指定一的一方实体类字节码
cascade:指定要使用的级联操作
fetch:指定是否采用延迟加载
optional:关联是否可选。如果设置为false,则必须始终存在非空关系。
@JoinColumn
作用:用于定义主键字段和外键字段的对应关系。
属性:
name:指定外键字段的名称
referencedColumnName:指定引用主表的主键字段名称
unique:是否唯一。默认值不唯一
nullable:是否允许为空。默认值允许。
insertable:是否允许插入。默认值允许。
updatable:是否允许更新。默认值允许。
columnDefinition:列的定义信息。
/**
主表的配置注解:
* 放弃外键的维护权
* mappedBy:对方配置关系的属性名称
* cascade : 配置级联(可以配置到设置多表的映射关系的注解上)
* cascadeType.all :所有
* merge :更新
* persist :保存
* remove :删除
* fetch:配置关联对象的加载方式
* EAGER : 立即加载
* LAZY : 延迟加载
*/
@OneToMany(mappedBy = "customer",cascade = CascadeType.ALL)
private Set<LinkMan> linkMans=new HashSet<LinkMan>();
从表的注解配置:
/**
* 配置联系人到客户的多对一关系
* 使用注解的形式配置多对一关系
* 1.配置表关系
* @ManyToOne : 配置多对一关系
* targetEntity:对方的实体类字节码
* 2.配置外键(中间表)
*
* 配置外键的过程,配置到了多的一方,就会在多的一方维护外键
*/
@ManyToOne(targetEntity = Customer.class,fetch = FetchType.LAZY)
@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
private Customer customer;
- 删除操作的说明如下
删除从表数据:可以随时任意删除。
删除主表数据:
有从表数据
- 在默认情况下,它会把外键字段置为null,然后删除主表数据。如果在数据库的表 结构上,外键字段有非空约束,默认情况就会报错了。
- 如果配置了放弃维护关联关系的权利,则不能删除(与外键字段是否允许为null, 没有关系)因为在删除时,它根本不会去更新从表的外键字段了。
- 如果还想删除,使用级联删除引用
没有从表数据引用:随便删
在实际开发中,级联删除请慎用!(在一对多的情况下)
- 级联操作
级联操作:指操作一个对象同时操作它的关联对象
使用方法:只需要在操作主体的注解上配置cascade
第4章 JPA中的多对多
- 表关系确立
@ManyToMany 作用:用于映射多对多关系 属性: cascade:配置级联操作。 fetch:配置是否采用延迟加载。 targetEntity:配置目标的实体类。映射多对多的时候不用写。 @JoinTable 作用:针对中间表的配置 属性: nam:配置中间表的名称 joinColumns:中间表的外键字段关联当前实体类所对应表的主键字段 inverseJoinColumn:中间表的外键字段关联对方表的主键字段 @JoinColumn 作用:用于定义主键字段和外键字段的对应关系。 属性: name:指定外键字段的名称 referencedColumnName:指定引用主表的主键字段名称 unique:是否唯一。默认值不唯一 nullable:是否允许为空。默认值允许。 insertable:是否允许插入。默认值允许。 updatable:是否允许更新。默认值允许。 columnDefinition:列的定义信息。
- 从表关联属性注解
在多对多(保存)中,如果双向都设置关系,意味着双方都维护中间表,都会往中间表插入数据,中间表的2个字段又作为联合主键,所以报错,主键重复,解决保存失败的问题:只需要在任意一方放弃对中间表的维护权即可,推荐在被动的一方放弃,配置如下:
@ManyToMany(mappedBy = "roles") private Set<User> users=new HashSet<User>();
- 主表关联属性注解
/** * 配置用户到角色的多对多关系 * 配置多对多的映射关系 * 1,声明关系的设置 * @ManyToMany(targetEntity = Role.class) //多对多 * targetEntity: 代表对方实体类的字节码 * 2,配置中间表(包含两个外键) * @JoinTable * name: 中间表的名称 * joinColumns:当前对象在中间表中的外键 * @JoinColumn的数组 * name: 外键名 * referencedColumnName: 参照得主表的主键名 * nverseJoinColumn:对方对象在中间表的外键 */ @ManyToMany(targetEntity = Role.class,cascade = CascadeType.ALL) @JoinTable(name = "sys_user_role", //joinColumns: 当前对象在中间表中的外键 joinColumns = {@JoinColumn(name = "sys_user_id",referencedColumnName = "user_id")}, //inverseJoinColumns: 对方对象在中间表的外键 inverseJoinColumns = {@JoinColumn(name = "sys_role_id",referencedColumnName = "role_id")} ) private Set<Role> roles=new HashSet<Role>();
Spring Data JPA中的多表查询
- 对象导航查询
- 我们查询客户时,要不要把联系人查询出来?
分析:如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。如果我们查出来的,不使用时又会白白的浪费了服务器内存。
解决:采用延迟加载的思想。通过配置的方式来设定当我们在需要使用时,发起真正的查询。
配置方式:
/**
* 在客户对象的@OneToMany注解中添加fetch属性
* FetchType.EAGER :立即加载
* FetchType.LAZY :延迟加载
*/
@OneToMany(mappedBy="customer",fetch=FetchType.EAGER)
private Set<LinkMan> linkMans = new HashSet<>(0);
- 我们查询联系人时,要不要把客户查询出来?
分析:例如:查询联系人详情时,肯定会看看该联系人的所属客户。如果我们不查的话,在用的时候还要自己写代码,调用方法去查询。如果我们查出来的话,一个对象不会消耗太多的内存。而且多数情况下我们都是要使用的。
解决: 采用立即加载的思想。通过配置的方式来设定,只要查询从表实体,就把主表实体对象同时查出来
配置方式
/**
* 在联系人对象的@ManyToOne注解中添加fetch属性
* FetchType.EAGER :立即加载
* FetchType.LAZY :延迟加载
*/
@ManyToOne(targetEntity=Customer.class,fetch=FetchType.EAGER)
@JoinColumn(name="cst_lkm_id",referencedColumnName="cust_id")
private Customer customer;
- 使用Specification查询
/** * Specification的多表查询 */ @Test public void testFind() { Specification<LinkMan> spec = new Specification<LinkMan>() { public Predicate toPredicate(Root<LinkMan> root, CriteriaQuery<?> query, CriteriaBuilder cb) { //Join代表链接查询,通过root对象获取 //创建的过程中,第一个参数为关联对象的属性名称,第二个参数为连接查询的方式(left,inner,right) //JoinType.LEFT : 左外连接,JoinType.INNER:内连接,JoinType.RIGHT:右外连接 Join<LinkMan, Customer> join = root.join("customer",JoinType.INNER); return cb.like(join.get("custName").as(String.class),"传智播客1"); } }; List<LinkMan> list = linkManDao.findAll(spec); for (LinkMan linkMan : list) { System.out.println(linkMan); } }