SpringBoot整合JPA
JPA介绍
首先需要向大伙介绍一下 Jpa,Jpa(Java Persistence API)Java 持久化 API,它是一套 ORM 规范,而不是具体的实现,Jpa 的江湖地位类似于 JDBC,只提供规范,所有的数据库厂商提供实现(即具体的数据库驱动),
Hibernate 只是 ORM 框架的一种,上面列出来的 ORM 框架都是支持 JPA2.0 规范的 ORM 框架。既然它是一个规范,不是具体的实现,那么必然就不能直接使用(类似于 JDBC 不能直接使用,必须要加了驱动才能用),我们使用的是具体的实现,在这里我们采用的实现实际上还是 Hibernate。
Spring Boot 中使用的 Jpa 实际上是 Spring Data Jpa,Spring Data 是 Spring 家族的一个子项目,用于简化 SQL、NoSQL 的访问,在 Spring Data 中,只要你的方法名称符合规范,它就知道你想干嘛,不需要自己再去写 SQL。
基本配置:
1、依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
2、配置文件
spring:
# 数据库配置
datasource:
url: jdbc:mysql://47.96.141.44:3306/jsptest?useUnicode=true&characterEncoding=utf-8
password: 147258369
username: root
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
# 显示sql
jpa:
show-sql: true
# 数据库为mysql
database: mysql
# # 数据库平台
database-platform: org.hibernate.dialect.MySQL57Dialect
# # 每次启动项目的时候 数据库初始化策略
hibernate:
ddl-auto: update
hbm2ddl.auto有四个属性:
- create:每次加载 hibernate 时都会删除上一次的生成的表,然后根据你的 model 类再重新来生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表数据丢失的一个重要原因。[删除-创建-操作]
- create-drop :每次加载 hibernate 时根据 model 类生成表,但是 sessionFactory 一关闭,表就自动删除。[删除-创建-操作-再删除]
- update:最常用的属性,第一次加载 hibernate 时根据 model 类会自动建立起表的结构(前提是先建立好数据库),以后加载 hibernate 时根据 model 类自动更新表结构,即使表结构改变了,但表中的行仍然存在,不会删除以前的行。要注意的是当部署到服务器后,表结构是不会被马上建立起来的,是要等应用第一次运行起来后才会。[没表-创建-操作 | 有表-更新没有的属性列-操作]
- validate:每次加载 hibernate 时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。[启动验证表结构,验证不成功,项目启动失败]
3、接口配置
dao接口继承 JpaRepository 接口 第一个参数为操作的实体类型 ,第二个参数为主键的类型
public interface StudentDao extends JpaRepository<Student,String> {
}
3.1根据名称自动生成SQL
Keyword | Sample | JPQL snippet |
---|---|---|
And | findByLastnameAndFirstname | … where x.lastname = ?1 and x.firstname = ?2 |
Or | findByLastnameOrFirstname | … where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals | findByFirstname,findByFirstnameIs | … 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 ages) | … 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) |
// 根据方法名生成sql语句 realname包含指定字符
List<SysUser> findByRealnameContaining(String realname);
//realname 以指定字符开头
List<SysUser> findByRealnameStartsWith(String startrealname);
3.2自定义Sql语句查询
参数的两种不同传递方式
①利用下标索引传参,索引参数如下所示,索引值从1开始,查询中 ”?X” 个数需要与方法定义的参数个数相一致,并且顺序也要一致:
@Query("select u from t_user u where id>?1 and username like ?2")
List<User> selectUserByParam(Long id, String name)
②命名参数(推荐):这种方式可以定义好参数名,赋值时采用@Param(“参数名”),而不用管顺序:
@Query("select u from t_user u where id>:id and username like :name")
List<User> selectUserByParam2(@Param("name") String name, @Param("id") Long id);
使用原生的 SQL 查询:
查询:
//使用自定义sql时,必须加nativeQuery设为true 为本地sql
@Query(value = "select * from sys_user where realname like concat('%',:realname,'%')",nativeQuery = true)
List<SysUser> findAllByRealnamelike(@Param("realname") String realname);
分页查询:
@Query(value = "select * from sys_user where orgguid=:orgguid",nativeQuery = true)
Page<SysUser> findByOrleguidPage(@Param("orgguid") String orgguid, Pageable pageable);
**注意:**传入Pageable中的页码要减1
//页码
int pageNum=4;
//页的大小
int pageSize=1;
Pageable pageable= PageRequest.of(pageNum-1,pageSize);
Page<SysUser> sysUserPage = sysUserDao.findByOrleguidPage("ZNG007", pageable);
PageResult pageResult = PageUtis.getPageResult(sysUserPage);
System.out.println(pageResult.toString());
public class PageUtis {
public static <T> PageResult getPageResult(Page<T> page) {
PageResult pageResult = new PageResult();
//页码
pageResult.setPageNum(page.getNumber()+1);
//页的大小
pageResult.setPageSize(page.getSize());
//元素总数
pageResult.setTotalSize(page.getTotalElements());
//页的总数
pageResult.setTotalPages(page.getTotalPages());
/**
* 获取当前页数的记录数
*/
pageResult.setContent(page.getContent());
return pageResult;
}
}
@Setter
@Getter
public class PageResult {
/**
* 页码
*/
private int pageNum;
/**
* 每页的大小
*/
private int pageSize;
/**
* 总记录数
*/
private long totalSize;
/**
* 总页数
*/
private int totalPages;
/**
* 记录模型
*/
private List<?> content;
@Override
public String toString() {
return "PageResult{" +
"pageNum=" + pageNum +
", pageSize=" + pageSize +
", totalSize=" + totalSize +
", totalPages=" + totalPages +
", content=" + content +
'}';
}
}
3.2自定义sql语句更新和删除
dao接口:
@Modifying
@Query(value = "update sys_user set realname=:realname where guid=:guid",nativeQuery = true)
int updateRealnameByGuid(@Param("realname")String realname,@Param("guid")String guid);
@Modifying
@Query(value = "delete from sys_user where guid=:guid",nativeQuery = true)
int deleteByGuid(@Param("guid")String guid);
service服务层:
@Service
@Transactional //注意这个@Transactional事务必须加 否则会报错
public class SysUserService {
@Autowired
private SysUserDao sysUserDao;
public int updateRealnameByGuid(String realname, String guid){
return sysUserDao.updateRealnameByGuid(realname,guid);
}
public int deleteByGuid(String guid){
return sysUserDao.deleteByGuid(guid);
}
}
注意:
1、可以通过自定义的 JPQL 完成 UPDATE 和 DELETE 操作. 注意: JPQL 不支持使用 INSERT 2、方法的返回值应该是 int,表示更新语句所影响的行数 3、在调用的地方必须加事务,没有事务不能正常执行 4、默认情况下, Spring Data 的每个方法上有事务, 但都是一个只读事务. 他们不能完成修改操作
Spring Data 提供了默认的事务处理方式,即所有的查询均声明为只读事务。对于自定义的方法,如需改变 Spring Data 提供的事务默认方式,可以在方法上添加 @Transactional 注解。 进行多个 Repository 操作时,也应该使它们在同一个事务中处理,按照分层架构的思想,这部分属于业务逻辑层,因此,需要在Service 层实现对多个 Repository 的调用,并在相应的方法上声明事务。 总结:
事务管理只有在service加上事务管理才起作用,query不需要事务管理但是delete update但需要事务管理为了不在Service层不加事务管理可以再Repository层的delete update加上@transactional 但这样不能真正保持事务的完整性.
4、@Transactional使用的注意事项
- Spring 基于注解的声明式事物 @Transactional 默认情况下只会对运行期异常(java.lang.RuntimeException及其子类)和 Error 进行回滚。
- 数据库引擎要支持事物,使用InnoDB。
- @Transactional 只能被应用到public方法上, 对于其它非public的方法,如果标记了@Transactional也不会报错,但方法没有事务功能.
具体使用场景:
- 在service方法中不使用try-catch显示处理异常,直接
throw new runtimeexcetpion()
可实现事务回滚 - 在service方法中使用try-catch,但是需要在catch中加上
throw new runtimeexcetpion()
可实现事务回滚 - 注意当方法加上synchronized时,由于锁的作用范围比事务的作用范围小,因此应该修改锁的作用范围,保证锁的范围比事务的范围大即可。
JPA多数据源
1、数据库配置
spring:
# 数据库配置
datasource:
url: jdbc:mysql://47.96.141.44:3306/jsptest?useUnicode=true&characterEncoding=utf-8
password: 147258369
username: root
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
# 数据源一
one:
username: root
password: 147258369
url: jdbc:mysql://47.96.141.44:3306/multipleDataSource1?useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
# 数据源二
two:
username: root
password: 147258369
url: jdbc:mysql://47.96.141.44:3306/multipleDataSource2?useUnicode=true&characterEncoding=UTF-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
# 显示sql
jpa:
show-sql: true
# 数据库为mysql
database: mysql
# # 数据库平台
database-platform: org.hibernate.dialect.MySQL57Dialect
# # 每次启动项目的时候 数据库初始化策略
hibernate:
ddl-auto: update
2、数据源配置
@Configuration
public class DataSourceConfig {
// 默认的主数据源,当有多个数据源时,使用@Primary标记为默认数据源
@Primary
@Bean
@ConfigurationProperties(prefix = "spring.datasource")
public DataSource dataSource(){
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.one")
public DataSource dataSourceOne(){
return DruidDataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties(prefix = "spring.datasource.two")
public DataSource dataSourceTwo(){
return DruidDataSourceBuilder.create().build();
}
}
2.1、主数据源详细配置
@Configuration
//扫描的dao接口的位置 引用自定义entityManagerFactoryRef 和 transactionManagerRef
@EnableJpaRepositories(basePackages = "com.lc.jpa.dao.dao1",entityManagerFactoryRef = "localContainerEntityManagerFactoryBean",transactionManagerRef = "platformTransactionManager")
public class JpaConfig {
@Autowired
@Qualifier("dataSource")
DataSource dataSource;
@Autowired
JpaProperties jpaProperties;
//配置 LocalContainerEntityManagerFactoryBean 一般也要设置为主FactoryBean
@Bean
@Primary
LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean(EntityManagerFactoryBuilder builder){
return builder.dataSource(dataSource)
//持久化单元
.persistenceUnit("pu")
//读取配置文件中jpa的配置
.properties(jpaProperties.getProperties())
//实体的位置
.packages("com.lc.jpa.entity")
.build();
}
//事务
@Bean
PlatformTransactionManager platformTransactionManager(EntityManagerFactoryBuilder builder){
LocalContainerEntityManagerFactoryBean factoryBean=localContainerEntityManagerFactoryBean(builder);
return new JpaTransactionManager(factoryBean.getObject());
}
}
2.2、第二数据源详细配置
@Configuration
@EnableJpaRepositories(basePackages = {"com.lc.jpa.dao.dao2"},entityManagerFactoryRef ="localContainerEntityManagerFactoryBeanOne",transactionManagerRef = "platformTransactionManagerOne")
public class JpaConfigOne {
@Autowired
@Qualifier("dataSourceOne")
DataSource dataSourceOne;
@Autowired
JpaProperties jpaProperties;
@Bean
LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBeanOne(EntityManagerFactoryBuilder builder){
return builder.dataSource(dataSourceOne)
.properties(jpaProperties.getProperties())
.packages("com.lc.jpa.entity")
//持久化单元
// entitymanagerfactory是一个工厂类用于产生entitymanager.在整个应用程序中,entitymanagerfactory应该被缓存,并且针对每一个持久化单元,它只应调用一次。
.persistenceUnit("pu1")
.build();
}
@Bean
PlatformTransactionManager platformTransactionManagerOne(EntityManagerFactoryBuilder builder){
LocalContainerEntityManagerFactoryBean factoryBean=localContainerEntityManagerFactoryBeanOne(builder);
return new JpaTransactionManager(factoryBean.getObject());
}
}
2.3、 第三数据源详细配置
@Configuration
@EnableJpaRepositories(basePackages = {"com.lc.jpa.dao.dao3"},entityManagerFactoryRef = "localContainerEntityManagerFactoryBeanTwo",transactionManagerRef = "platformTransactionManagerTwo")
public class JpaConfigTwo {
@Autowired
@Qualifier("dataSourceTwo")
DataSource dataSourceTwo;
@Autowired
private JpaProperties jpaProperties;
@Bean
LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBeanTwo(EntityManagerFactoryBuilder builder){
return builder.dataSource(dataSourceTwo)
.packages("com.lc.jpa.entity")
.properties(jpaProperties.getProperties())
.persistenceUnit("pu2")
.build();
}
@Bean
PlatformTransactionManager platformTransactionManagerTwo(EntityManagerFactoryBuilder builder){
LocalContainerEntityManagerFactoryBean factoryBean=localContainerEntityManagerFactoryBeanTwo(builder);
return new JpaTransactionManager(factoryBean.getObject());
}
}
3、dao接口配置
这里只使用 主数据源进行演示
public interface SysUserDao extends JpaRepository<SysUser,String> {
// 根据方法名生成sql语句 realname包含指定字符
List<SysUser> findByRealnameContaining(String realname);
//realname 以指定字符开头
List<SysUser> findByRealnameStartsWith(String startrealname);
// 使用自定义sql时,必须加nativeQuery设为true 为本地sql
@Query(value = "select * from sys_user where realname like concat('%',:realname,'%')",nativeQuery = true)
List<SysUser> findAllByRealnamelike(@Param("realname") String realname);
@Query(value = "select * from sys_user where orgguid=:orgguid",nativeQuery = true)
Page<SysUser> findByOrleguidPage(@Param("orgguid") String orgguid, Pageable pageable);
@Modifying
@Query(value = "update sys_user set realname=:realname where guid=:guid",nativeQuery = true)
int updateRealnameByGuid(@Param("realname")String realname,@Param("guid")String guid);
@Modifying
@Query(value = "delete from sys_user where guid=:guid",nativeQuery = true)
int deleteByGuid(@Param("guid")String guid);
}
注意:因为所有的修改和删除都是基于事务的,所以一般在service层加上**@Transactional("platformTransactionManager")**
4、service层
@Service
@Transactional(value = "platformTransactionManager")
public class SysUserService {
@Autowired
private SysUserDao sysUserDao;
public int updateRealnameByGuid(String realname, String guid){
return sysUserDao.updateRealnameByGuid(realname,guid);
}
public int deleteByGuid(String guid){
return sysUserDao.deleteByGuid(guid);
}
}