Spring Data JPA的使用

28 篇文章 2 订阅
6 篇文章 0 订阅

概念

什么是JPA

JPA(Java Persistence API)是Sun官方提出的Java持久化规范,它为Java开发人员提供一种对象关联映射工具来管理Java应用中的关系数据,它的出现主要是为了简化现有持久化开发工作和整合ORM技术。

Spring Data JPA

Spring Data JPA是Spring基于ORM框架、JPA规范的基础上封装的一套JPA应用框架,可以让开发者用极简的代理即可实现对数据的访问和操作,提供包括增、删、改、查等在内的常用功能。Spring Data JPA其实就是spring基于Hibernate之上构建的JPA使用解决方案。

构建

添加依赖
	 	<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
添加配置文件
spring:
	jpa:
	    properties:
	      hibernate:
	        hbm2ddl:
	           auto: create
	           dialect: org.hibernate.dialect.MySQL5InnoDBDialect
	        format_sql: true
	    show-sql: true

hibernate.hbm2ddl.auto参数的作用主要用于:自动创建、更新、验证数据表结构,一共有四个值;

  • create:每次加载Hibernate时都会删除上一次生成的表,然后根据model类再重新生成新表,哪怕两次没有任何改变也要这样执行,这就是导致数据库表丢失的一个重要原因;
  • create-drop:每次加载Hibernate时根据model类生成表,但是sessionFactory一关闭,表就自动删除;
  • update:最常用的属性,第一次加载Hibernate时根据model类会自动建立起表的结构,以后加载Hibernate时根据model类自动更新表结构,即使表结构改变了,但是表中的行仍然存在,不会删除以前的行,应该注意的是,当部署到服务器之后,表结构不会马上被建立起来。是要等应用第一次运行起来之后才会;
  • valudate:每次加载Hibernate时,验证创建数据表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。

dialect主要是指定生成表名的存储引擎为InnoDB

show-sql是否在日志中打印出自动生成的SQL,方便调用时查看

实体类
@Entity
@Data
public class User {
    @Id
    @GeneratedValue
    private Long id;
    @Column(nullable = false,unique = true)
    private String userName;
    @Column(nullable = false,unique = true)
    private String passWord;
    @Column(nullable = false,unique = true)
    private String email;
    @Column(nullable = true,unique = true)
    private String nickName;
    @Column(nullable = false)
    private String regTime;
}
  • @Entity(name="EntityName")必须,用来标注一个数据库对应的实体,数据库中创建表名默认和类名一直,其中,name为可选,对应数据库中一个表,使用此注解标记Pojo是一个JPA实体;

  • @Table(name="",catalog="",schema="")可选,用来标注一个数据库对应的实体,数据库中创建的表名默认和类名一致,通常和@Entity配置实用,只能标注在实体的class定义处,表示实体对应的数据表信息;

  • @Id必须,定义了映射到数据表的主键的属性,一个实体只能有一个属性被映射为主键;

  • @GeneratedValue(strategy=GenerationType,genetator="")可选,strategy表示主键生成策略,由AUTO、INDENTUTY、SEQUENCE和TABLE四种,分别表示让ORM框架自动选择,generator表示主键生成器的名称|

  • @Column(name="user_code",nullable=false,length=32)可选,描述了数据库表中该字段的详细定义,这对于根据JPA注解生成数据库表结构的工具,name表示数据库表中该字段的名称,默认情形睡醒名称一致,nullable表示该字段是否允许为null,默认为true;unique表示该字段是否是唯一标识,默认为false;length表示该字段大小,仅对string类型字段有效;

  • @Transient可选,表示该属性并非一个到数据库表的字段的映射,ORM框架将忽略该属性;

  • @Enumerated可选,使用枚举类的时候,希望数据库中存储的是枚举对应的String类型,而不是枚举的索引值,需要在属性上添加Enumerated(EnumType.STRING)注解;

Repository构建

创建Repository只需要继承JPARepository即可,会帮我们自动生成很多内置方法,另外还有一个非常使用的功能,可以根据方法名自动产生SQL。

public interface UserRepository extends JpaRepository<User,Long> {
    User findByUserName(String userName);
    User findByUserNameOrEmail(String userName,String email);
}

只需在对应的Repository中创建好方法,使用的时候直接将接口注入到类中调用即可。
在这里插入图片描述
JPARepository继承PagingAndSortingRepositoryQueryByExampleExecutor,PagingAndSortingRepository主要负责排序和分页内容,QueryByExampleExecutor提供了很多示例的查询方法:

public interface QueryByExampleExecutor<T> {
    <S extends T> Optional<S> findOne(Example<S> var1);

    <S extends T> Iterable<S> findAll(Example<S> var1);

    <S extends T> Iterable<S> findAll(Example<S> var1, Sort var2);

    <S extends T> Page<S> findAll(Example<S> var1, Pageable var2);

    <S extends T> long count(Example<S> var1);

    <S extends T> boolean exists(Example<S> var1);
}

PagingAndSortingRepository继承自CrudRepository

public interface CrudRepository<T, ID> extends Repository<T, ID> {
    <S extends T> S save(S var1);

    <S extends T> Iterable<S> saveAll(Iterable<S> var1);

    Optional<T> findById(ID var1);

    boolean existsById(ID var1);

    Iterable<T> findAll();

    Iterable<T> findAllById(Iterable<ID> var1);

    long count();

    void deleteById(ID var1);

    void delete(T var1);

    void deleteAll(Iterable<? extends T> var1);

    void deleteAll();
}

测试
@RunWith(SpringRunner.class)
@SpringBootTest
class UserRepositoryTest {

    @Resource
    private UserRepository userRepository;

    @Test
    public void test(){
        Date date = new Date();
        DateFormat dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG);
        String formattedData = dateFormat.format(date);
        userRepository.save(new User(1L,"aa","aa@126.com","aa","aa123456",formattedData));
        userRepository.save(new User(2L,"bb","bb@126.com","bb","bb123456",formattedData));
        userRepository.save(new User(3L,"cc","cc@126.com","cc","cc123456",formattedData));
        userRepository.save(new User(4L,"dd","dd@126.com","dd","dd123456",formattedData));

        Assert.assertEquals(9,userRepository.findAll().size());
        Assert.assertEquals("bb",userRepository.findByUserNameOrEmail("bb","cc@126.com").getNickName());
        userRepository.delete(userRepository.findByUserName("aa1"));

    }
}

基本查询

可以将Spring Data JPA查询分为两种,一种是Spring Data JPA默认实现的,另一种是需要根据查询的情况来自行构建。

预生成方法

因为继承了JpaRepository而拥有了父类的的内容

自定义查询

Spring Data JPA可以根据接口方法名来实现数据库操作,主要语法如下:

根据用户名查询用户:

User findByUserName(String userName);

也可以加一些关键字如And、or:

User findByUserNameOrEmail(String userName,String email);

修改、删除、统计也是类似方法:

Long deleteById(Long id);
Long countByUserName(String userName);

基本上SQL体系中的关键词都可以使用,如LIKE、IgnoreCase、OrderBy:

List<User> findByEmailLike(String email);

User findByUserNameIgnoreCase(String userName);
 
List<User> findByUserNameOrderByEmailDesc(String email);
KeywordSampleJPQL sinppet
AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname= ?2
OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname =?2
Is,EqualsfindByFirstnameIs,findByFirstnameEquals… 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 (parameterbound with appended %)
EndingWithfindByFirstnameEndingWith… where x.firstname like ?1 (parameterbound with prepended %)
ContainingfindByFirstnameContaining… where x.firstname like ?1 (parameterbound 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 age)… where x.age not in ?1
TRUEfindByActiveTrue()… where x.active = true
FALSEfindByActiveFalse()… where x.active = false
IgnoreCasefindByFirstnameIgnoreCase… where UPPER(x.firstame) = UPPER(?1)

Spring Data JPA的高级用法

自定义SQL查询

在SQL的查询方法上面使用@Query注解,在注解内写Hql来查询内容

   @Query("select u from User u")
   Page<User> findALL(Pageable pageable);

同时还支持原生SQL,需要再添加一个参数nativeQuery=true

@Query(value = "select * from user u where u.nick_name =?1",nativeQuery = true)
Page<User> findByNickName(String nickName,Pageable pageable);

其中,“1”代表的参数的顺序,如果有多个参数可以通过这样来指定,还可以使用@Param来支持;

@Query(value = "select * from user u where u.nick_name =:nickName",nativeQuery = true)
Page<User> findByNickName(@Param("nickName") String nickName, Pageable pageable);

如果涉及到删除和修改需要加上@Modifying,也可以根据需要加上@Transactional对事物的支持、操作超时设置等;

 	@Transactional(timeout = 10)
    @Modifying
    @Query("update User set userName = ?1 where id = ?2")
    int modifyById(String userName,Long id);
    
    @Override
    @Transactional
    @Modifying
    @Query("delete from User where id=?1")
    void deleteById(Long id);
使用已命令的查询

除了使用@Query外,还可以预先定义好一些查询,并为其命名,然后在Repository中添加相同命名的方法。

@NamedNativeQueries({
        @NamedNativeQuery(name = "User.findByPassWord"
                ,query = "select u from User u where u.passWord = ?1"),
        @NamedNativeQuery(name = "User.findByNickName111"
                ,query = "select u from User u where u.nickName = ?1"),
})
public class User {
	...
}
 List<User> findByPassWord(String passWord);

 List<User> findByNickName(String nickName);
Query查找策略

我们可以通过三种方法来定义Query:

  1. 通过方法名自动创建Query;
  2. 通过@Query注解实现自定义Query;
  3. 通过@NamedQuery注解来定义Query;

可以通过配置@EnableJpaRepositoriesqueryLookupStrategy属性来配置Query的查找策略:

  • CREATE:尝试从查询方法名构造特定于存储的查询,一般的方法是从方法名中删除一组已知的前缀并解析方法的其余部分;

  • USE_DECLARED_QUERY:尝试查找已经声明的查询,如果找不到则抛出异常,查询可以通过某个地方的注释定义,也可以通过其他方式声明;

  • CREATE_IFNOTFOUND(默认):首先查找一个已经声明的查询,如果没有找到,它将创建一个自定义方法基于名称的查询,允许通过方法名进行快速查询定义,还可以根据需要引入声明的查询来定制这些查询调优。

分页查询

Spring Data JPA已经帮我们内置了分页功能,在查询的方法中,需要传入参数Pageable,当查询中有多个参数的时候Pageable建议作为最后一个参数传入。

@Query("select u from User u")
Page<User> findALL(Pageable pageable);
Page<User> findByNickName(String nickName, Pageable pageable);

Pageable是Spring封装的分页实现类,使用的时候需要传入页数、每页条数和排序规则,Page是Spring封装的分页对象,封装了总页数、分页数据等,返回对象除使用Page外,还可以使用Slice作为返回值;

Slice<User> findByNickNameAndEmail(String nickName, String email,Pageable pageable
);

Page和Slice的区别如下:

  • Page接口继承自Slice接口,而Slice继承自Iterable接口;
  • Page接口扩展了Slice接口,添加了获取总页数和元素总数量的方法,因此返回Page接口时,必须执行两条SQL,一条负责查询分页数据,另一条负责统计数据数量;
  • 返回Slice结果时,查询的SQL只会有查询分页数据这一条,不统计数据数量;
   		int page=1,size=2;
        Sort sort = Sort.by(Sort.Direction.DESC, "id");
         Pageable pageable = PageRequest.of(page, size, sort);
        System.out.println(userRepository.findALL(pageable).getTotalElements());
         userRepository.findByNickName("aa",pageable);
限制查询

有时候我们只需要查询前N个元素,或者只取前一个实体;

User findFirstByOrderByLastnameAsc();
User findTopByOrderByAgeDesc();
Page<User> queryFirst10ByLastname(String lastname, Pageable pageable);
List<User> findFirst10ByLastname(String lastname, Sort sort);
List<User> findTop10ByLastname(String lastname, Pageable pageable);
复杂查询

可以通过AND获取OR等连接词来不断拼接属性来构建多条件查询,但是如果参数大于6个时,方法名会变得非常长,并且还不能解决动态条件查询的场景。

JpaSpecificationExecutor是JPA2.0提供的CriteriaAPI的使用封装,可以动态生成Query来满足我们业务中的各种复杂场景,Spring Data JPA为我们提供了JPASpecificationExecutor接口,只要简单实现toPredicate方法就可以实现复杂的查询。

public interface JpaSpecificationExecutor<T> {
	 //根据 Specification 条件查询单个对象,注意的是,如果条件能查出来多个会报错
	 T findOne(@Nullable Specification<T> spec);
	 //根据 Specification 条件查询 List 结果
	 List<T> findAll(@Nullable Specification<T> spec);
	 //根据 Specification 条件,分⻚查询
	 Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);
	 //根据 Specification 条件,带排序的查询结果
	 List<T> findAll(@Nullable Specification<T> spec, Sort sort);
	 //根据 Specification 条件,查询数量
	 long count(@Nullable Specification<T> spec);
}
  • Root<T> root代表了可以查询和操作的实体对象的根,通过get(“属性名”)来获取对应的值;
  • CriteriaQuery<?> query:代表一个specific的顶层查询对象,它包含着查询的各个部分;
  • CriteriaBuilder cb来构建CriticaQuery的构建器对象,相当于条件或条件组合,并以Predicate形式返回。
@Entity
@Data
public class UserDetail {

    @Id
    @GeneratedValue
    private Long id;
    @Column(nullable = false,unique = true)
    private Long userId;
    private Integer age;
    private String status;
    private String realName;
    private String hobby;
    private String introduction;
    private String lastLoginIp;
}
public interface UserDetailRepository extends JpaSpecificationExecutor<UserDetail>
        ,JpaRepository<UserDetail,Long> {

}
public interface UserDetailService {
    public Page<UserDetail> findByCondition(DetailParam detailParam, Pageable pageable);
}
@Service
public class UserDetailServiceImpl implements UserDetailService{

    @Resource
    private UserDetailRepository userDetailRepository;

    @Override
    public Page<UserDetail> findByCondition(DetailParam detailParam, Pageable pageable) {
        return userDetailRepository.findAll((root,query,cb)->{
            ArrayList<Predicate> predicates = new ArrayList<>();
            // equal使用
            if (!StringUtils.isEmpty(detailParam.getIntroduction())){
                predicates.add(cb.equal(root.get("introduction"),detailParam.getIntroduction()));
            }
            //like使用
            if (!StringUtils.isEmpty(detailParam.getRealName())){
                predicates.add(cb.like(root.get("realName"),"%"+detailParam.getRealName()+"%"));
            }
            //between
            if (detailParam.getMinAge()!=null && detailParam.getMaxAge()!=null){
                predicates.add(cb.between(root.get("age"),detailParam.getMinAge(),detailParam.getMaxAge()));
            }
            //greaterThan 大于等于
            if (detailParam.getMaxAge()!=null){
                predicates.add(cb.greaterThan(root.get("age"),detailParam.getMinAge()));
            }
            return query.where(predicates.toArray(new Predicate[predicates.size()])).getRestriction();
        },pageable);
    }
}

测试

@Test
    void findByCondition() {
        int page=0,size=10;
        Sort sort = Sort.by(Sort.Direction.DESC, "id");
        Pageable pageable = PageRequest.of(page, size, sort);
        DetailParam param = new DetailParam();
        param.setIntroduction("Development5");
        param.setMaxAge(25);
        param.setMinAge(15);
        Page<UserDetail> page1 = userDetailService.findByCondition(param, pageable);
        for (UserDetail userDetail : page1) {
            System.out.println(userDetail);
        }
    }
多表查询

多表查询在Spring Data JPA中有两种实现方式,第一种是利用Hibernate的级联查询来实现,第二种是创建一个结果集的接口来接收连表查询后的结果;

接收结果集

public interface UserInfo {
    String getUserName();
    String getEmail();
    String getAddress();
    String getHobby();
}
@Query("select u.userName as userName, u.email as email, d.introduction as introduction , " +
            "d.hobby as hobby from User u , UserDetail d " +
            "where u.id=d.userId and d.hobby = ?1 ")
    List<UserInfo> findUserInfo(String hobby);
@Test
    public void testUserInfo(){
        List<UserInfo> userInfos = userDetailRepository.findUserInfo("eat2");
        for (UserInfo userInfo : userInfos) {
            System.out.println(String.format("%s %s %s %s",userInfo.getAddress(),userInfo.getEmail()
                    ,userInfo.getHobby(),userInfo.getUserName()));
        }
    }
Hibernate: 
    select
        user0_.user_name as col_0_0_,
        user0_.email as col_1_0_,
        userdetail1_.introduction as col_2_0_,
        userdetail1_.hobby as col_3_0_ 
    from
        user user0_ cross 
    join
        user_detail userdetail1_ 
    where
        user0_.id=userdetail1_.user_id 
        and userdetail1_.hobby=?

Spring Data JPA 多数据源的使用

多数据源的支持

配置两个数据源

datasource:
    primary:
      url: jdbc:mysql://localhost:3306/test1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
      username: root
      password: 12345678
      driver-class-name: com.mysql.cj.jdbc.Driver
    secondary:
      url: jdbc:mysql://localhost:3306/test2?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
      username: root
      password: 12345678
      driver-class-name: com.mysql.cj.jdbc.Driver

创建DataSourceConfig添加@Configuration注解,在项目启动时运行初始化数据库资源。

@Configuration
public class DataSourceConfig {

    @Bean(name = "primaryDataSource")
    @Primary
    @ConfigurationProperties("spring.datasource.primary")
    public DataSource firstDataSource(){
        return DataSourceBuilder.create().build();
    }
    
    @Bean(name = "secondaryDataSource")
    @ConfigurationProperties("spring.datasource.secondary")
    public DataSource secondaryDataSource(){
        return DataSourceBuilder.create().build();
    }
}

@Primary作为默认数据源使用;

加载JPA的相关配置,JPAProperties时JPA的一些属性配置信息,构建LocalEntityManagerFactoryBean需要参数信息注入到方法中。

    @Autowired
    private JpaProperties jpaProperties;
    @Autowired
    private HibernateProperties hibernateProperties;

	@Bean(name = "vendorProperties")
    public Map<String,Object> getVendorProperties(){
        return hibernateProperties.determineHibernateProperties(
                jpaProperties.getProperties()
                ,new HibernateSettings());
    }

第一个数据源的加载配置过程

LocalEntityManagerFactoryBean负责创建一个适合于仅使用JPA进行数据访问的环境的EntityManager,构建的时候需要指明提示实体类的包路径、数据源和JPA配置信息;

@Bean(name = "entityManagerFactoryPrimary")
    @Primary
    public LocalContainerEntityManagerFactoryBean entityManagerFactoryPrimary(
            EntityManagerFactoryBuilder builder){
        return builder
                .dataSource(primaryDataSource)
                .properties(vendorproperties)
                .packages("com.example.demo")
                .persistenceUnit("primaryPersistenceUnit")
                .build();
    }
    
    @Bean(name = "entityManagerPrimary")
    @Primary
    public EntityManager entityManager(EntityManagerFactoryBuilder builder){
        return entityManagerFactoryPrimary(builder).getObject().createEntityManager();
    }

EntityMabager是JPA中用于增、删、改、查的接口,相当于一座桥梁,连接内存中的Java对象和数据库数据存储,使用EntityManager中的相关接口对数据库实体进行操作的时候,EntityManager会跟踪实体对象的状态,并决定在特定时刻对实体的操作映射到数据库操作上面。
同时给数据源添加上JPA事务;

    @Bean(name = "transactionManagerPrimary")
    @Primary
    PlatformTransactionManager transactionManagerPrimary(EntityManagerFactoryBuilder builder){
        return new JpaTransactionManager(entityManagerFactoryPrimary((builder)).getObject());
    }

将我们在类中配置好的EntityManager和事务信息注入到对应数据源的repository目录下,这样目录下的repository就会拥有对应数据源和事务信息;


@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "entityMabagerFactoryPrimary",
        transactionManagerRef = "transactionManagerPrimary",
        basePackages = { "com.example.demo.repository" }
)
public class PrimaryConfig {
}

第二个数据源的加载配置过程

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
        entityManagerFactoryRef = "entityMabagerFactorySecondary",
        transactionManagerRef = "transactionManagerSecondary",
        basePackages = { "com.example.demo.repository" }
)
public class SecondaryConfig {
    @Autowired
    @Qualifier("secondaryDataSource")
    private DataSource secondaryDataSource;
    @Autowired
    @Qualifier("vendorProperties")
    private Map<String,Object> vendorproperties;

    @Bean(name = "entityManagerFactorySecondary")
    public LocalContainerEntityManagerFactoryBean entityManagerFactorySecondary(
            EntityManagerFactoryBuilder builder){
        return builder
                .dataSource(secondaryDataSource)
                .properties(vendorproperties)
                .packages("com.example.demo")
                .persistenceUnit("secondaryPersistenceUnit")
                .build();
    }

    @Bean(name = "entityManagerSecondary")
    public EntityManager entityManager(EntityManagerFactoryBuilder builder){
        return entityManagerFactorySecondary(builder).getObject().createEntityManager();
    }

    @Bean(name = "transactionManagerSecondary")
    PlatformTransactionManager transactionManagerSecondary(EntityManagerFactoryBuilder builder){
        return new JpaTransactionManager(entityManagerFactorySecondary((builder)).getObject());
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值