Spring Data Jpa学习笔记
ORM思想及JPA规范
JDBC
JDBC自身理解
我们之前学习的JDBC本身也是一套规范,MySQL和Oracle等数据库公司根据规范开发驱动,我们便实现一套Java代码,便可操作数据库,在不改变Java代码的情况下,修改Driver驱动便可实现操作数据库
JDBC连接数据库的代码
这里以MySQL为例子,回顾着写一下链接数据库的代码:
- Statement方法
/**
* Statement方法
*/
//注册驱动
class.forName(com.mysql.jdbc.com);
//建立链接
Connection con = DriverManager.getConnection(url,user,password);
//创建对象
Statement st = createStatement();
//执行查询返回结果集
String sql = "select * from user_table";
Result rs = st.excuteQuery(sql);
- PreparStatement方法
/**
* PreparStatement方法
*/
String sql = "select * from user_table";
//注册驱动
class.forName(com.mysql.jdbc.com);
//建立链接
Connection con = DriverManager.getConnection(url,user,password);
//创建对象
PrepardStatement pst = con.preparStatement(sql);
pst.setString(1,);//从1编号,写每个占位符?的值
//执行查询返回结果集
Result result = pst.executeQuery();
试问Statement 和 preparStatement 的区别
方法区别在于preparStatement的提高性能 预处理 提高安全性
重点说下安全性 在登陆账号密码的时候 password稍加改动便可绕过验证直接登录
例如sql语句为:
select * from user_table where name = '' and password = ''
如果用户密码 password 输入的值为 ==’ or ’ 1 ’ = ’ 1 ==
则sql语句变为:
select * from user_table where name = '随意' and password = '' or ' 1 ' = ' 1 '
//则OR 1 =1 为 true 直接登陆成功
preparStatement方法用占位符的预处理避免了这个问题。
ORM思想:
ORM : 对象关系映射
在面向对象开发的过程中的,通过ORM就可把对象映射到关系型数据库中,使程序能够做到建立对象和数据库的关联,用户操作实体类对象便可以操作数据库
建立两个映射关系
- 建立实体类和表的映射关系
- 建立实体类中的属性和表中字段的映射关系
目的:
- 操作实体类就相当于操作数据库表
实现ORM思想的框架有
- Mybatis
- Hibernate
- …
JPA规范
JPA规范由SUN公司定义的一套规范,内部由接口和抽象类组成。
实现JPA规范的框架有hibernate 和 toplink 等框架。hibernate除了作为ORM框架之外,也是一种JPA实现。我们以hibernate作为了解JPA规范的框架。
JPA搭建环境
- 创建maven工程导入坐标
- 配置jpa的核心配置文件
- 编写客户的实体类
- 配置实体类和表,实体类中的属性和表中的字段的映射关系
- 增删改查
1 创建maven工程导入坐标
pom.xml
<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>
<!-- MySQL驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<!-- log4j 日志-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!-- lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
</dependencies>
2 配置JPA的核心配置文件
JPA的核心配置文件位于:类路径下一个META-INF文件夹下。命名为:persistence.xml
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="2.0">
<!-- 需要配置persistence-unit节点
持久化单元
name:持久化单元名称,可以随便起名字
transaction-type:事务管理的方式
TA:分布式事务管理
RESOURCE_LOCAL:本地事务管理
-->
<persistence-unit name="myJpa" transaction-type="RESOURCE_LOCAL">
<!--jpa的快速实现方式-->
<!--数据库信息-->
<!--可选配置:配置jpa实现方的配置信息-->
<!--hibernate作为实现方式-->
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<properties>
<!--数据库信息(用户名&密码&驱动&数据库地址)-->
<property name="javax.persistence.jdbc.user" value="root"/>
<property name="javax.persistence.jdbc.password" value="root"/>
<property name="javax.persistence.jdbc.diver" value="com.mysql.jdbc.Driver"/>
<property name="javax.persistence.jdbc.url" value="jdbc:mysql:///jpa"/>
<!--可选配置:配置jpa实现方的配置信息
显示sql : true|false
自动创建数据库表 : create|update|none-->
<property name="hibernate.show_sql" value="true"/>
<property name="hibernate.hbm2ddl.auto" value="update"/>
</properties>
</persistence-unit>
</persistence>
注意自动创建数据库表的三个命令区别:hibernate.hbm2ddl.auto
- create 创建表(如果数据库中有表,则先删除表,再建新表)
- update 创建表(如果数据库有表,不删除)
- none 不建表
3 编写客户的实体类
因为我使用了LomBok插件,所以省略了getter、setter、toString方法
Customer.java
/**
* 客户实体类
*/
@Data
public class Customer {
private Long custId;//主键
private String custName;//名称
private String custSource;//客户来源
private String custLevel;//客户级别
private String custIndustry;//客户所属行业
private String custPhone;//联系方式
private String custAddress;//地址
}
4 配置实体类和表,类中属性和表中字段映射关系
配置实体类和表
- @Entity: 声明实体类
- @Table: 实体类映射的表 name = “表名”
配置实体类中属性和表中字段
- @Id: 声明主键
- @GeneratedValue 配置主键的生成策略
- Column(name = “字段名”)
/**
* 客户实体类
*/
/**
* 第一步:配置实体类与表的映射关系
* Entity: 声明实体类
* Table: 实体类映射的表 name = "表名"
*/
@Data
@Entity
@Table(name = "cst_customer")
public class Customer {
/**
* 第二步:配置属性与表的字段名
* Id: 声明主键
* GeneratedValue 配置主键的生成策略
* (strategy = GenerationType.IDENTITY) 自增
* 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;//地址
}
各个注解在代码块中有注释。
5 JPA测试CRUD
操作步骤:
- 加载配置文件创建实体管理类工厂对象 Persistence.createEntityManagerFactory();
- 通过实体管理器工厂获取实体管理器 factory.createEntityManager();
- 创建并开启事务 em.getTransaction();
- CRUD
- 提交事务(回滚事务)tx.commit();
- 释放资源
加载配置文件创建实体管理类工厂对象 | Persistence.createEntityManagerFactory(); |
---|---|
通过实体管理器工厂获取实体管理器 | factory.createEntityManager(); |
创建 | entityManager.getTransaction(); |
开启事务 | transaction.begin(); |
CRUD | 见下表 |
提交事务(回滚事务) | transaction.commit(); |
释放资源 |
persist | 保存 |
---|---|
merge | 更新 |
remove | 删除 |
find | 查询(根据id) |
getReference | 查询(根据id) |
find 和 getReference 的区别:
find : 立即加载 凡调用到find方法便立即发送查询sql语句
getReference : 延迟加载 执行方法不发送sql语句 什么时候用到,什么时候发送查询语句
public class CustomerTest {
@Test
public void testSave(){
//1.加载配置文件创建工厂(实体管理类工厂)对象
EntityManagerFactory factory = Persistence.createEntityManagerFactory("myJpa");//myJpa为配置文件设置的持久层名字
//2.通过实体管理类工厂获取实体管理器
EntityManager em = factory.createEntityManager();
//3.获取事务对象
EntityTransaction tx = em.getTransaction();
tx.begin();//开启事务
//4.完成增删改查--保存一个客户
Customer customer = new Customer();
customer.setCustName("奥里斯");
customer.setCustSource("CSDN");
customer.setCustIndustry("CSDNBLOG");
customer.setCustLevel("NO.1");
customer.setCustAddress("人民路");
customer.setCustPhone("1888888888");
em.persist(customer);//保存操作
//5.提交事务
tx.commit();
//6.释放资源
em.close();
factory.close();
}
}
JpaUtils类
由于每次执行持久层和数据库打交道的操作,便创建一次实体管理类工厂,这样的缺点是浪费资源和耗时
所以我们可以创建一个JpaUtils.java 利用静态代码块的方法实现只执行一次创建工厂的操作
JpaUtils.java
/**
* 创建公共的工厂对象
*
* 为了解决EntityManagerFactory实体管理器工厂的浪费资源和耗时问题
*/
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
/**
* 通过静态代码块的形式:当第一次访问此工具类的时候,创建一个公共的实体管理器工厂对象
* 第二次访问:直接通过一个创建好的公共工厂factory对象,创建实体管理器EntityManager
*/
public class JpaUtils {
private static EntityManagerFactory factory;
static {
factory = Persistence.createEntityManagerFactory("myJpa");
}
//工厂生产实体管理器
public static EntityManager getEntityManager(){
return factory.createEntityManager();
}
}
增加
public void testSave(){
//创建实体管理器
EntityManager entityManager = JpaUtils.getEntityManager();
//创建事务
EntityTransaction transaction = entityManager.getTransaction();
//开始事务
transaction.begin();
//创建客户对象 然后 保存操作
Customer customer = new Customer();
customer.setCustName("张峰");
customer.setCustSource("爱唱歌");
customer.setCustIndustry("我是歌手");
customer.setCustLevel("NO.2");
customer.setCustAddress("朝阳区");
customer.setCustPhone("1868666666");
//实体管理器保存实体
entityManager.persist(customer);
//提交事务
transaction.commit();
//释放资源
entityManager.close();
}
更新
public void testUpdate(){
//创建实体管理器
EntityManager entityManager = JpaUtils.getEntityManager();
//创建事务
EntityTransaction transaction = entityManager.getTransaction();
//开始事务
transaction.begin();
/**
* merge 方法更新
* 更新分为三步:
* 1 --> 先根据id查询,并赋值给实体类对象
* 2 --> 实体类set方法重新赋值需要更新的值
* 3 --> merge方法更新
*/
//1.查询
Customer customer = entityManager.find(Customer.class, 1l);
//2.赋值
customer.setCustName("三哥");
//3.merge方法更新
entityManager.merge(customer);
//提交事务
transaction.commit();
//释放资源
entityManager.close();
}
删除
public void testDelete(){
//创建实体管理器
EntityManager entityManager = JpaUtils.getEntityManager();
//创建事务
EntityTransaction transaction = entityManager.getTransaction();
//开始事务
transaction.begin();
/**
* remove 方法删除
* 删除分为两步:
* 1 --> 先根据id查询想要删除的数据 赋值给一个对象
* 2 --> 删除对象
*/
Customer customer = entityManager.find(Customer.class, 2l);
//remove删除
entityManager.remove(customer);
//提交事务
transaction.commit();
//释放资源
entityManager.close();
}
查找
public void testFind(){
//创建实体管理器
EntityManager entityManager = JpaUtils.getEntityManager();
//创建事务
EntityTransaction transaction = entityManager.getTransaction();
//开始事务
transaction.begin();
//创建客户对象 然后 查询
/**
* 查询find和getReference --> 根据id查询
* class: 查询数据的结果需要包装的实体类类型字节码
* 比如查询为顾客信息customer,结果类型封装为customer
* id: 主键值
*
* 方法区别:
* find: 立即加载 凡调用到find方法便立即发送查询sql语句
* getReference: 延迟加载 执行方法不发送sql语句 什么时候用到,什么时候发送查询语句
*
*/
Customer customer = entityManager.find(Customer.class, 1l);//id为long长整型 需要专为1L
System.out.println(customer);
//提交事务
transaction.commit();
//释放资源
entityManager.close();
}
JPQL语句
jpql语句是操作实体类的
jpql语句与mysql语句相似
jpql 不支持select * ,但支持select 其他语句照搬然后微调
微调:表名修改为实体类名,字段名修改为实体类属性名
Jpql 语句是操作实体类的:
分为三步:
- 创建jpql对象
- 对参数赋值
- 查询返回封装结果集
必须创建query对象,query对象才是执行jpql的对象
查询全部(顺序)
//查询---顺序
public void testSelect(){
//1. 创建实体管理器
EntityManager em = JpaUtils.getEntityManager();
//2. 创建事务 并开启
EntityTransaction ts = em.getTransaction();
ts.begin();
/**
* jpql由于不支持select * , 表名对应映射的实体类名
* mysql : select * from cst_customer;
* jpql : from Customer
*/
//3. 创建Query对象, query对象才是执行jpql的对象
String jpql = "from Customer";
Query query = em.createQuery(jpql);
//query.getResultList()方法查询结果并封装结果集为list
List list = query.getResultList();
//打印
for (Object obj : list
) {
System.out.println(obj);//由于结果集中包含实体类属性中各个类型 所以用Object上帝类
}
//4. 提交事务
ts.commit();
//5. 释放资源
em.close();
}
查询全部(倒序)
//查询---倒序
public void testSelectDesc(){
//1. 创建实体管理器
EntityManager em = JpaUtils.getEntityManager();
//2. 创建事务 并开启
EntityTransaction ts = em.getTransaction();
ts.begin();
/**
* mysql : select * from cst_customer order by cust_id desc ;
* jpql : from Customer order by custId desc
*/
//3. 创建Query对象, query对象才是执行jpql的对象
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);
}
//4. 提交事务
ts.commit();
//5. 释放资源
em.close();
}
聚合函数–统计count
/**
* JPQL与MySQL语句相似,
* mysql中表名对应jpql中的实体类名,mysql中的字段名对应jpql中的实体类属性
* jpql不支持select * 但是支持select
*/
//统计查询
public void testCount(){
/**
* mysql : select count(cust_id) from cst_customer;
* jpql : select count(custId) from Customer;
*/
EntityManager entityManager = JpaUtils.getEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
//创建query对象
String jpql = "select count(custId) from Customer";
Query query = entityManager.createQuery(jpql);
//因为count计数只有一个 并不是结果集 所以不用getResultList()
//query.getSingleResult() 结果为单一的方法
Object singleResult = query.getSingleResult();
System.out.println(singleResult);
//提交事务
transaction.commit();
//释放资源
entityManager.close();
}
分页
//分页查询
public void testPage(){
/**
* mysql : select * from cst_customer limit 0,2;
* select * from cst_customer limit ?,?;
* jpql : from cst_customer limit ?,?
* ? 设置分页参数 jpql中的第二步
*/
//工厂创建实体管理器
EntityManager entityManager = JpaUtils.getEntityManager();
//实体管理器创建事务
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();//开启事务
//1. 创建query对象
String jpql ="from Customer";
Query query = entityManager.createQuery(jpql);
//2. 设置参数
query.setFirstResult(0);//设置起始索引
query.setMaxResults(2);//设置单次查询条数
//3. 封装结果集
List resultList = query.getResultList();
//for each 方法遍历打印
// for (Object obj : resultList
// ) {
// System.out.println(obj);
// }
//迭代器方法遍历打印
Iterator<Object> it = resultList.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
//提交事务
transaction.commit();
//释放资源
entityManager.close();
}
条件查询–模糊查询
//模糊查询
public void testLike(){
/**
* mysql : select * from cst_customer where cust_name like '张%';
* jpql : from cst_customer where cust_name like ?
*/
//工厂创建实体管理器
EntityManager entityManager = JpaUtils.getEntityManager();
//实体管理器创建事务
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();//开启事务
//1. 创建query对象
String jpql ="from Customer where custName like ?";
Query query = entityManager.createQuery(jpql);
//2. 设置参数 query.setParameter(第几个占位符,"value值");
query.setParameter(1,"张%");
//3. 封装结果集
List resultList = query.getResultList();
//迭代器方法遍历打印
Iterator<Object> it = resultList.iterator();
while(it.hasNext()){
System.out.println(it.next());
}
//提交事务
transaction.commit();
//释放资源
entityManager.close();
}
封装结果集 | query.getResultList(); |
---|---|
单个数据结果 | query.getSingleResult(); |
设置占位符和参数 | query.setParameter(第几个占位符,“value值”); |
起始索引 | query.setFirstResult(0); |
最大索引(每页条数) | query.setMaxResults(15); |
Spring Data Jpa
Spring Data Jpa概述
Spring Data Jpa是Spring基于ORM框架,JPA规范的基础上封装的一套JPA应用框架。
Spring Data Jpa使得我们解脱了DAO层的操作,基本上所有的CRUD都可以依赖它实现。
实际的工作用推荐Spring Data Jpa + ORM完成。这样优点是切换不同的ORM方便,使数据层更加简单,方便解耦。
简化数据访问层代码,使用了Spring Data Jpa,我们致谢dao层接口,便自动具备了基本的CRUD和分页查询等操作
Spring Data Jpa入门操作
基于上面的客户表案例
搭建SpringDataJpa环境
- 创建maven工程导入坐标
- 配置Spring的配置文件
创建maven
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.itcast</groupId>
<artifactId>jpa-day2</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<spring.version>5.0.2.RELEASE</spring.version>
<hibernate.version>5.0.7.Final</hibernate.version>
<slf4j.version>1.6.6</slf4j.version>
<log4j.version>1.2.12</log4j.version>
<c3p0.version>0.9.1.2</c3p0.version>
<mysql.version>5.1.6</mysql.version>
</properties>
<dependencies>
<!-- junit单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!-- spring beg -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.6.8</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- spring对orm框架的支持包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- spring end -->
<!-- hibernate beg -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.2.1.Final</version>
</dependency>
<!-- hibernate end -->
<!-- c3p0 beg -->
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>${c3p0.version}</version>
</dependency>
<!-- c3p0 end -->
<!-- log end -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!-- log end -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- spring data jpa 的坐标-->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.9.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- el beg 使用spring data jpa 必须引入 -->
<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>
<!-- el end -->
<!-- lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
</dependencies>
</project>
配置文件
配置Spring的配置文件,在类路径resource下,创建一个applicationContext.xml文件。配置spring data jpa与spring的整合
配置文件主要包括
- 创建连接池。
- 创建entityManagerFactory工厂对象交给spring容器管理
- 配置事务管理器
- 声明式事务
一般用在service层,本案例不涉及 - 整合spring data Jpa
- 配置包扫描
下面配置文件有三个包扫描,意思分别是:
扫描实体类所在包 | name=“packagesToScan” value=“com.iyiliang.domain” |
---|---|
扫描dao所在的包 | base-package=“com.iyiliang.dao” |
扫描所有spring注解的包 | base-package=“com.iyiliang” |
applicationContext.xml
<?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">
<!--spring 和 spring data jpa的配置-->
<!-- 1.创建entityManagerFactory对象交给spring容器管理-->
<bean id="entityManagerFactoty" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource" />
<!--配置的扫描的包(实体类所在的包)Spring容器一旦创建,便会在该路径下寻找带有@Entity @Table的实体类 -->
<property name="packagesToScan" value="com.iyiliang.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.创建数据库连接池 因为用的c3p0,所以用c3p0.ComboPooledDataSource类-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="root"></property>
<property name="password" value="root"></property>
<property name="jdbcUrl" value="jdbc:mysql:///jpa" ></property>
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
</bean>
<!--3.整合spring data Jpa-->
<!-- 配置dao所在的包,配置事务和工厂,因为spring data jpa封装的jpa,所以还要配置这些。-->
<jpa:repositories base-package="com.iyiliang.dao" transaction-manager-ref="transactionManager"
entity-manager-factory-ref="entityManagerFactoty" ></jpa:repositories>
<!--4.配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactoty"></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.声明式事务,一般用在service层,本案例不涉及 -->
<!-- 6. 配置包扫描 带有spring注解的包-->
<context:component-scan base-package="com.iyiliang" ></context:component-scan>
</beans>
编写实体类并配置映射关系
Customer.java
/**
* 客户实体类
*/
@Data
@Entity
@Table(name = "cst_customer")
public class Customer {
@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;//地址
}
编写dao层接口
编写一个符合Spring Data Jpa 的dao层接口
只需要写dao层接口,不需要编写dao层接口的实现类
dao层接口规范:
- dao层接口需要继承两个接口(JpaRepository<T, ID>,JpaSpecificationExecutor< T >)
- 需要提供相应的泛型
基础CRUD接口 | JpaRepository<实体类类型, 主键类型> |
---|---|
分页等复杂功能接口 | JpaSpecificationExecutor<实体类类型> |
/**
* 客户实体类接口
*
*/
public interface CustomerDao extends JpaRepository<Customer,Long>, JpaSpecificationExecutor<Customer> {
}
测试dao接口–spring data jpa内部方法实现CRUD
只写了这个dao接口 不写实现类,现在已经具备CRUD功能。下面简单的看几个吧。
用Junit4 首先建立test 测试 。然后因为交给了Spring容器完成所以指明@RunWith和@ContextConfiguration配置文件地址
/**
* 因为交给了Spring容器完成所以指明@RunWith和配置文件地址
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class CustomerDaoTest {
@Autowired
private CustomerDao customerDao;
}
查询
查询通过ID。 如果需要通过其他属性查询,需自己在接口写方法,下面具体讲解。
//查询一个
@Test
public void testFindOne() {
Customer customer = customerDao.findOne(3l);//ID为long型
System.out.println(customer);
}
查询所有 结果为List集合
//查询所有(List集合)
@Test
public void testFindAll() {
List<Customer> all = customerDao.findAll();
for (Object obj: all
) {
System.out.println(obj);
}
}
保存
保存
//测试保存方法 save()方法可用于新增和更新
@Test
public void testSave(){
//new一个客户
Customer customer = new Customer();
customer.setCustName("阿尔托");
customer.setCustPhone("011544886");
customer.setCustAddress("Alto City");
customer.setCustIndustry("Adventure冒险");
customer.setCustLevel("top1");
customer.setCustSource("推荐");
//保存这个客户信息
customerDao.save(customer);
}
修改
修改信息 先查询后修改
/**
* save方法用于保存和更新
* 当数据库中没有该id的时候,执行保存操作
* 当数据库中有该id时候,用find先查到赋值为customer,再用set方法设置修改的值
*/
@Test
public void testUpadate(){
//查询一个客服赋值为customer
Customer customer = customerDao.findOne(6l);//id为long型
customer.setCustPhone("011544886");
customer.setCustAddress("昌平区");
customer.setCustSource("推荐");
//保存这个客户信息
customerDao.save(customer);
}
删除
删除
//删除操作
public void testDelete(){
customerDao.delete(3l);//id为long型
}
SpringDataJpa运行原理分析
- 通过JdkDynamicAopProxy的invoke方法创建了一个动态代理对象 SimpleJpaRepository
- SimpleJpaRepository当中封装了JPA的操作(借助JPA的api完成数据库的CRUD)
- 通过hibernate完成数据库操作(封装了jdbc)
复杂查询(jpql,sql,方法名)
jpql查询方式和sql类似。除了上面讲到的sql操作数据库表,jpql操作实体类对象。还有一点需要修改
@Query里面有个nativeQuery属性
nativeQuery | true | false(默认) |
---|---|---|
本地查询 | sql查询 | jpql查询(默认) |
如果返回客户信息是多个,需要list集合
Jpql查询方式
对于一些内部没有定义的方法,比如上面只能通过ID查询。如果通过名字查询则自己定义。
1…写在dao接口
2…在新添加的方法上注解@Query(value = “jpql语句”)
下面的几个例子上面分别为dao接口里新增的方法!
下面的为Junit4单元测试用例!
例子1:
//根据姓名查询 from Customer where custName = ?
//因为需要返回一个客户信息 所以是customer类型
@Query(value = "from Customer where custName = ?")
public Customer findByName(String name);
//JPQL根据姓名查询
@Test
public void testFindByName(){
System.out.println(customerDao.findByName("卢哈哈"));
}
例子2:
/**
* 当有多个占位符时
* 一般参数顺序默认和占位符顺序保持一致
* 若顺序不想保持一致可以加索引(问号+数字)
*/
//根据姓名 手机号查询 from Customer where custName = ? and custPhone = ?
@Query(value = "from Customer where custName = ?2 and custPhone = ?1 ")
public Customer findByNameAndPhone(String phone, String name);
//根据姓名 手机号查询
@Test
public void testByNameAndPhone(){
Customer customer = customerDao.findByNameAndPhone("1888666666", "卢哈哈");
System.out.println(customer);
}
例子3:
/**
* @Query 为查询操作
* 要进行更新 需要加注解@Modifying
*/
//根据id修改姓名 update Customer set custName = ? where id = ?
//修改信息不用返回 所以void类型
@Modifying
@Query(value = "update Customer set custName = ?1 where custId = ?2")
public void updateNameById(String name,Long id);
//根据id修改姓名
@Test
@Transactional //添加事务支持
@Rollback(value = false) //默认回滚,所以设置回滚为"否"
public void testUpdateNameByName(){
customerDao.updateNameById("奥德赛",5l);
}
需要 注意 的是:
@Query执行的是查询
如果涉及到更新操作 ,需要加注解@Modifying
sql查询方式
需要注意的是sql特有的查询返回的是list的数组,所以可用Object [] ,理解为一维数组
下面的几个例子上面分别为dao接口里新增的方法!
下面的为Junit4单元测试用例!
例子1:
//sql模糊查询188开头手机号
@Query(value = "select * from cst_customer where cust_phone like ? ",nativeQuery = true)
public List<Object[]> findByPhone(String phone);
//sql模糊查询188开头手机号
@Test
public void testSqlByPhone(){
List<Object[]> customers = customerDao.findByPhone("188%");
for (Object[] obj : customers
) {
System.out.println(Arrays.toString(obj));
}
}
例子2:
//查询所有
@Query(value = "select * from cst_customer " , nativeQuery = true)
public List<Object []> findAllBySql();
//sql查询所有
@Test
public void testFindAllBySql(){
List<Object[]> cou = customerDao.findAllBySql();
for (Object[] c: cou
) {
System.out.println(Arrays.toString(c));
}
}
方法名查询
前方高能!!!
方法名规则不写jpql语句,只写符合spring data jpa 方法名称规范的方法名,便自动可实现查询
写法为:findBy 开头代表查询 + 属性名称(首字母大写)+查询方式(默认为=,还有其他Like,IsNull,Not等)+ And(Or)…写法和sql相同
看下面两个例子:
//findBy + 属性名称 (按照属性名称完成匹配)
public Customer findByCustName(String name);
//findBy + 属性名称 + “查询方式(Like | IsNull 等等)”
//多条件查询加 And | Or 等
public List<Customer> findByCustLevelAndCustPhoneLike(String level, String phone);
Specifications动态查询
上面学习的是基于接口JpaRepository<T,ID>实现的CURD,现在学习第二个接口JpaSpecificationExecutor< T >
specification 规范 executor 执行人
JpaSpecificationExecutor 方法列表
- T findOne(Specification spec); //查询单个对象
- List< T > findAll(Specification spec); //查询列表
- Page< T > findAll(Specification spec, Pageable pageable);
- List< T > findAll(Specification spec, Sort sort);
- long count(Specification spec);//统计查询
查询步骤
Specification :查询条件
- 自定义我们的Specification实现类,
- 即创建匿名内部类实现topredicatef方法
- 借助参数完成查询
- root : 获取需要的对象属性
- CriteriaQuery:顶层查询对象,自定义查询方式
- CriteriaBuilder : 构造的条件(like模糊查询,equal等于查询。。等)
例子1
例子1:借助名字查询
//Spec按姓名查询
@Test
public void testSpecSelectByName(){
//匿名内部类
Specification<Customer> sp = new Specification<Customer>() {
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
//root.get(按需求获取实体类对象属性)
Path<Customer> custName = root.get("custName");
//cb.equal(path对象,比较的值) 精准查询
Predicate cb = criteriaBuilder.equal(custName,"阿尔托");
//返回结果封装的cb
return cb;
}
};
//调用返回的结果
Customer one = customerDao.findOne(sp);
System.out.println(one);
}
例子2
例子2:
实现了多条件、模糊、排序三个函数
多条件和模糊组合查并按着ID排序
//多条件和模糊查询---行业精准NO.1和手机号模糊1888%
@Test
public void testMoreSpecAndLike(){
//创建Specification方法
Specification<Customer> spec = new Specification<Customer>() {
public Predicate toPredicate(Root<Customer> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
//第一个条件属性
Path<Customer> custLevel = root.get("custLevel");
//第二个条件属性
Path<Customer> custPhone = root.get("custPhone");
//取值
Predicate eq1 = criteriaBuilder.equal(custLevel, "NO.1");
/**
* like,gt,lt,等模糊查询方法与equal方法不同
* equal直接得到path对象(属性)进行比较;
* 模糊查询需要得到path,根据path指定参数类型比较
* 形式:cb.like(path.as(类型的字节码对象))
*/
Predicate eq2 = criteriaBuilder.like(custPhone.as(String.class),"188%");
// return eq2;
//合并条件eq1 eq2
Predicate and = criteriaBuilder.and(eq1,eq2);
return and;
}
};
/**
* 排序
* 实例化sort对象 并传入两个参数
* 第一个: 正序 Sort.Direction.ASC | 倒序 Sort.Direction.DESC
* 第二个 : 排序的属性名称
*/
Sort sort = new Sort(Sort.Direction.ASC,"custId");
List<Customer> all = customerDao.findAll(spec,sort);
for (Object obj : all
) {
System.out.println(obj);
}
}
例子3
例子3,分页
/**
* 分页
* 创建Pageable对象
*
* PageRequest(起始, 每页条数);
*/
@Test
public void tesPage(){
Specification<Customer> spec = null ;
Pageable pageable = new PageRequest(0, 3);
Page<Customer> pg = customerDao.findAll(spec, pageable);
System.out.println(pg.getContent());//得到数据集合列表
// System.out.println(pg.getSize()); //每页长度
System.out.println(pg.getTotalElements());//得到总条数
System.out.println(pg.getTotalPages());//得到总页数
}
ps:个人觉得没有方法名和自定义方法用jpql语句简单。。。
完成多表操作
分析步骤:
- 明确表关系(一对一 | 一对多 | 多对多)
- 确定表关系(确定那个为主表(一)那个为从表(多),或者都为 多,然后明确外键或者建立中间表)
- 编写实体类,在实体类中描述表关系
- 一的实体类创建一个多的实体类的集合
- 多的实体类创建一个一的实体类对象
- 配置映射关系
一对多
假设客户为一个公司为和 联系人为员工 形成 一对多。
步骤
- 明确表关系(一对多)
- 确定表关系。主表为客户公司。从表为联系人员工
- 编写实体类,在实体类中描述表的包含关系。多的实体类 包含 一个 的对象。。。。。 一个的 包含多的实体类的 集合
- 配置映射关系。jpa注解配置一对多的映射关系
//配置客户和联系人之间的关系(一对多关系)
/**
* 使用注解的形式配置多表关系
* 1.声明关系
* @OneToMany : 配置一对多关系
* targetEntity :对方对象的字节码对象
* 2.配置外键(中间表)
* @JoinColumn : 配置外键
* name:外键字段名称
* referencedColumnName:参照的主表的主键字段名称
*
* * 在客户实体类上(一的一方)添加了外键了配置,所以对于客户而言,也具备了维护外键的作用
*
*/
@OneToMany(targetEntity = LinkMan.class)
@JoinColumn(name = "lkm_cust_id",referencedColumnName = "cust_id")
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;
由于双方都配置了联系,建立了双向链接。会发送两条insert语句,一条多余的update语句,那我们的解决是思路很简单,就是一的一方放弃维护权。
一般一的一方放弃。直接在注解上修改
/**
*放弃外键维护权的配置将如下配置改为
*/
//@OneToMany(targetEntity=LinkMan.class)
//@JoinColumn(name="lkm_cust_id",referencedColumnName="cust_id")
//设置为
@OneToMany(mappedBy="customer")
多对多
假设
两张表分别为 用户表 和 角色表
步骤
- 明确表关系 (多对多)
- 确定表关系(建立中间表)
- 编写实体类,并描述表的包含关系:分别建立对方对象的集合。
- 配置映射关系
/**
* 配置用户到角色的多对多关系
* 配置多对多的映射关系
* 1.声明表关系的配置
* @ManyToMany(targetEntity = Role.class) //多对多
* targetEntity:代表对方的实体类字节码
* 2.配置中间表(包含两个外键)
* @JoinTable
* name : 中间表的名称
* joinColumns:配置当前对象在中间表的外键
* @JoinColumn的数组
* name:外键名
* referencedColumnName:参照的主表的主键名
* inverseJoinColumns:配置对方对象在中间表的外键
*/
@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<>();
级联操作
- 从表有数据,默认会把外键字段置为null,然后删除主表数据。如果在数据库的表结构上,外键字段有非空约束,默认情况就会报错了。
- 如果配置了放弃维护关联关系的权利,则不能删除(与外键字段是否允许为null, 没有关系)因为在删除时,它根本不会去更新从表的外键字段了。
- 如果还想删,使用级联删除
- 没有表数据引用可删除
级联操作:指操作一个对象同时操作它的关联对象
使用方法:
在注解上配置cascade = “persist | remove | merge | all ”
这几个词和jpa操作数据库意思一样
@OneToMany(mappedBy="customer",cascade=CascadeType.ALL)
对象导航查询
通过已加载的对象,查询他的关联对象
查询一个客户,获取该客户下的所有联系人
@Autowired
private CustomerDao customerDao;
@Test
//由于是在java代码中测试,为了解决no session问题,将操作配置到同一个事务中
@Transactional
public void testFind() {
Customer customer = customerDao.findOne(5l);
Set<LinkMan> linkMans = customer.getLinkMans();//对象导航查询
for(LinkMan linkMan : linkMans) {
System.out.println(linkMan);
}
}
延迟加载与立即加载
/**
* 在联系人对象的@ManyToOne注解中添加fetch属性
* FetchType.EAGER :立即加载
* FetchType.LAZY :延迟加载
*/
@ManyToOne(targetEntity=Customer.class,fetch=FetchType.EAGER)
@JoinColumn(name="cst_lkm_id",referencedColumnName="cust_id")
private Customer customer;
默认情况下,一关联多采用延迟加载。多连一采用立即加载