SpringBoot整合JPA

Lou.Chen
大约 9 分钟

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
KeywordSampleJPQL snippet
AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2
Is,EqualsfindByFirstname,findByFirstnameIs… where x.firstname = ?1
BetweenfindByStartDateBetween… where x.startDate between ?1 and ?2
LessThanfindByAgeLessThan… where x.age < ?1
LessThanEqualfindByAgeLessThanEqual… where x.age <= ?1
GreaterThanfindByAgeGreaterThan… where x.age > ?1
GreaterThanEqualfindByAgeGreaterThanEqual… where x.age >= ?1
AfterfindByStartDateAfter… where x.startDate > ?1
BeforefindByStartDateBefore… where x.startDate < ?1
IsNullfindByAgeIsNull… where x.age is null
IsNotNull,NotNullfindByAge(Is)NotNull… where x.age not null
LikefindByFirstnameLike… where x.firstname like ?1
NotLikefindByFirstnameNotLike… where x.firstname not like ?1
StartingWithfindByFirstnameStartingWith… where x.firstname like ?1(parameter bound with appended %)
EndingWithfindByFirstnameEndingWith… where x.firstname like ?1(parameter bound with prepended %)
ContainingfindByFirstnameContaining… where x.firstname like ?1(parameter bound wrapped in %)
OrderByfindByAgeOrderByLastnameDesc… where x.age = ?1 order by x.lastname desc
NotfindByLastnameNot… where x.lastname <> ?1
InfindByAgeIn(Collection ages)… where x.age in ?1
NotInfindByAgeNotIn(Collection ages)… where x.age not in ?1
TruefindByActiveTrue()… where x.active = true
FalsefindByActiveFalse()… where x.active = false
IgnoreCasefindByFirstnameIgnoreCase… 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 进行回滚。
  • image-20220627110538600
  • 数据库引擎要支持事物,使用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);
    }

}