SpringBoot学习小结之JPA

SpringBoot学习小结之JPA,第1张

前言

​ JPA (Java Persistence API),最初于 2006 年 5 月 11 日发布,是一个为 Java 开发人员提供ORM的Java 规范,用于管理 Java 应用程序中的关系数据

​ JPA 是规范,Hibernate是实现。在springboot-data-jpa中,底层使用了 Hibernate 的 JPA 技术实现

一、基本使用

下面演示在Springboot中如何使用Jpa,pom如下

<parent>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-parentartifactId>
    <version>2.6.7version>
    <relativePath/> 
parent>

<dependencies>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-data-jpaartifactId>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
        dependency>

        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
            <scope>runtimescope>
        dependency>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <optional>trueoptional>
        dependency>
        <dependency>
            <groupId>com.google.guavagroupId>
            <artifactId>guavaartifactId>
            <version>30.0-jreversion>
        dependency>
        <dependency>
            <groupId>com.querydslgroupId>
            <artifactId>querydsl-jpaartifactId>
            <scope>providedscope>
        dependency>
        <dependency>
            <groupId>com.querydslgroupId>
            <artifactId>querydsl-aptartifactId>
            <scope>providedscope>
        dependency>
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <scope>testscope>
        dependency>
    dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombokgroupId>
                            <artifactId>lombokartifactId>
                        exclude>
                    excludes>
                configuration>
            plugin>

            <plugin>
                <groupId>com.mysema.mavengroupId>
                <artifactId>apt-maven-pluginartifactId>
                <version>1.1.3version>
                <executions>
                    <execution>
                        <goals>
                            <goal>processgoal>
                        goals>
                        <configuration>
                            <outputDirectory>target/generated-sources/javaoutputDirectory>
                            <processor>com.querydsl.apt.jpa.JPAAnnotationProcessorprocessor>
                        configuration>
                    execution>
                executions>
            plugin>
        plugins>
    build>

application.yml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 
    url: jdbc:mysql://localhost:3306/test01?characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: update
    properties:
      hibernate:
        dialect: org.hibernate.dialect.MySQL57Dialect
        enable_lazy_load_no_trans: true

spring.jpa.hibernate.ddl-auto有以下4个选项

  • create 启动时删数据库中的表,然后创建,退出时不删除数据表

  • create-drop 启动时删数据库中的表,然后创建,退出时删除数据表 如果表不存在报错

  • update 启动时表格式不一致则更新表,原有数据保留

  • validate 启动时对表结构进行校验 如果不一致则报错

BaseRepository.java

@NoRepositoryBean
public interface BaseRepository<T,I> extends JpaRepository<T,I>, JpaSpecificationExecutor<T>, QuerydslPredicateExecutor<T> {
}

RoleRepository.java

public interface RoleRepository extends BaseRepository<Role, Integer> {
    List<Role> findRolesByNameStartingWith(String name);
}

UserRepository.java

public interface UserRepository extends BaseRepository<User, Integer> {
}

User.java

@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String username;
    private String password;
    private String salt;
    private LocalDate birthdate;
    private LocalDateTime lastLoginTime;

    @Builder.Default
    @ToString.Exclude
    @ManyToMany
    @JoinTable(name = "user_role",
            joinColumns = { @JoinColumn(name = "user_id", referencedColumnName = "id")},
            inverseJoinColumns = { @JoinColumn(name = "role_id", referencedColumnName = "id")})
    private Set<Role> roles = Sets.newHashSet();

}

Role.java

@Entity
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Role {

    private static final Logger logger = LoggerFactory.getLogger(Role.class);

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    private String name;

    private LocalDateTime createTime;

    @Builder.Default
    @ToString.Exclude
    @ManyToMany(mappedBy = "roles")
    private Set<User> users = Sets.newHashSet();

}

测试文件

@SpringBootTest
@Transactional
class DemojpaApplicationTests {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private RoleRepository roleRepository;

    @Autowired
    private JPAQueryFactory jpaQueryFactory;

    private static final Logger logger = LoggerFactory.getLogger(DemojpaApplicationTests.class);

    @BeforeEach
    void setUp() {
        User user1 = User.builder().username("赵立").password("qiwjqieq1w31").salt("avewe32")
                .birthdate(LocalDate.of(2000, 1, 1))
                .lastLoginTime(LocalDateTime.of(2022, 2, 2, 2, 2, 2)).build();

        User user2 = User.builder().username("张三").password("VEBCEDVqew").salt("vwew233dv")
                .birthdate(LocalDate.of(2001, 2, 2))
                .lastLoginTime(LocalDateTime.of(2022, 1, 1, 1, 1, 1)).build();

        Role role1 = Role.builder().name("管理员").createTime(LocalDateTime.now()).build();
        Role role2 = Role.builder().name("会计组").createTime(LocalDateTime.now()).build();
        Role role3 = Role.builder().name("开发组").createTime(LocalDateTime.now()).build();
        Set<Role> roles = Sets.newHashSet(role1, role2, role3);

        roleRepository.saveAll(roles);

        user1.setRoles(roles);
        user2.setRoles(Sets.newHashSet(role2, role3));

        userRepository.saveAll(Lists.newArrayList(user1, user2));
    }



    @Test
    void testUserPageQuery() {

        Page<Role> rolePages = roleRepository.findAll(PageRequest.of(0, 2));

        logger.info("nums:{}, pages:{}, list:{}", rolePages.getTotalElements(), rolePages.getTotalPages(), rolePages.get().collect(Collectors.toList()));

        assertThat(rolePages.getTotalElements()).isEqualTo(3);
        assertThat(rolePages.getTotalPages()).isEqualTo(2);
        assertThat(rolePages.getContent().size()).isEqualTo(2);

    }

    @Test
    void testCriteriaSimpleQuery() {
        Specification<User> specification =  (root, query, criteriaBuilder) -> {

            Predicate namePred = criteriaBuilder.like(root.get("username").as(String.class),"%赵%");
            Predicate datePred = criteriaBuilder.between(root.get("lastLoginTime").as(LocalDateTime.class), LocalDateTime.of(2020, 2, 2, 2, 2, 2), LocalDateTime.now());

            return criteriaBuilder.or(namePred, datePred);
        };
        List<User> all = userRepository.findAll(specification);
        logger.info("名字带赵或者上次登录时间在2020-2-2 2:2:2到现在的用户:{}", all);

        assertThat(all.size()).isEqualTo(2);

    }

    @Test
    void testCriteriaJoinQuery() {
        Specification specification = (root, query, criteriaBuilder) -> {

            Join role = root.join("roles", JoinType.LEFT);
            Predicate p1 = criteriaBuilder.equal(role.get("name"), "管理员");
            Predicate p2 = criteriaBuilder.equal(root.get("username"), "张三");
            return criteriaBuilder.and(p1, p2);
        };

        List all = userRepository.findAll(specification);

        logger.info("名字叫张三的管理员:{}", all);
        assertThat(all.size()).isEqualTo(0);
    }

    @Test
    void testExample() {

        User user = User.builder().username("赵").build();
        ExampleMatcher exampleMatcher = ExampleMatcher.matching()
                .withMatcher("username", matcher -> matcher.contains());

        List<User> allUser = userRepository.findAll(Example.of(user, exampleMatcher));

        logger.info("users:{}", allUser);
        assertThat(allUser.size()).isEqualTo(1);

    }

    @Test
    void testQueryDsl() {
        QUser qUser = QUser.user;
        QRole qRole = QRole.role;


        List<User> users = jpaQueryFactory.selectFrom(qUser).where(qUser.username.eq("张三")).fetch();
        assertThat(users.size()).isEqualTo(1);

        List<User> userList = jpaQueryFactory.selectFrom(qUser).leftJoin(qUser.roles, qRole).where(qRole.name.eq("管理员")).fetch();
        assertThat(userList.size()).isEqualTo(1);

        com.querydsl.core.types.Predicate p = qUser.birthdate.between(LocalDate.of(1996, 1, 1), LocalDate.of(2002, 1, 1))
                .or(qUser.username.contains("赵"));


        Iterable<User> all = userRepository.findAll(p);
        List<User> userList2 = Lists.newArrayList(all);

        assertThat(userList2.size()).isEqualTo(2);

    }

    @Test
    void testRoleQuery() {
        List<Role> groups = roleRepository.findRolesByNameContaining("组");
        assertThat(groups.size()).isEqualTo(2);
    }
}
二、常用注解

以下注解都位于javax.persistence包下,详细说明可以查看官方源码

注解名Target含义
@IdMETHOD, FIELD指定数据库的主键,每个实体必须有
@IdClassTYPE指定该类的联合主键实体
@EnityTYPE指定该类是被jpa管理的实体
@TableTYPE指定数据库的表名
@ColumnMETHOD, FIELD指定该属性对应数据库列名
@TransientMETHOD, FIELD指定该属性并非是实体映射到数据库的字段
@GeneratedValueMETHOD, FIELD指定主键生成策略
@BasicMETHOD, FIELD指定该属性是实体映射到数据库的字段,默认属性都加@Basic
@TemporalMETHOD, FIELD指定时间(java.util.Data,java.util.Calendar)属性的精度
@EnumeratedMETHOD, FIELD指定映射枚举字段,默认下标
@LobMETHOD, FIELD指定该属性是一个大对象
@OneToOneMETHOD, FIELD指定该属性和另一个实体有1对1的关联
@OneToManyMETHOD, FIELD指定该属性和另一个实体有1对多的关联
@ManyToManyMETHOD, FIELD指定该属性和另一个实体有多对1的关联
@ManyToOneMETHOD, FIELD指定该属性和另一个实体有多对多的关联
@MappedSuperclassTYPE指定该类不是一个完整的实体类,不会映射到数据库表中,即不会创建它对应的表,但是其属性都将映射到其子类的数据库字段中
@JoinColumnMETHOD, FIELD定义实体的外键关联字段,通常和OneToOne、ManyToOne、OneToMany一起使用
@OrderByMETHOD, FIELD定义查询时排序规则
@JoinTableMETHOD, FIELD定义多对多关联关系时的关联表
@NamedEntityGraphTYPE使用该注解可以提高查询效率,解决n+1问题,可以和org.springframework.data.jpa.repository.EntityGraph配合使用
@PreUpdate ,@PrePersist,@PreRemoveMETHOD在update,persist,remove前调用该方法
@PostLoad,@PostPersist,@PostRemove,@PostUpdateMETHOD在save,persist,remove,update后调用该方法

jpa注解

注解名Target含义
@QueryMETHOD, ANNOTATION_TYPE自定义query方法,默认使用hql, 原生sql需要使用nativeQuery=true
@ModifyingMETHOD, ANNOTATION_TYPE使用@Query进行非查询时,需要加上这个
@LockMETHOD, ANNOTATION_TYPE为当前方法执行加锁,锁有6种类型,具体可以查看官方文档
三、主要接口

spring-data-jpa相比mybatis而言,强大的一点就是不用编写sql语句,仅通过特定的规则构造方法名,就能从数据库查询特定的数据,相应的缺点就是只能单表查询。规则如下所示

关键字例子JPQL 代码片段
DistinctfindDistinctByLastnameAndFirstnameselect distinct … where x.lastname = ?1 and x.firstname = ?2
AndfindByLastnameAndFirstname… where x.lastname = ?1 and x.firstname = ?2
OrfindByLastnameOrFirstname… where x.lastname = ?1 or x.firstname = ?2
Is, EqualsfindByFirstname,findByFirstnameIs,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
IsNull, NullfindByAge(Is)Null… 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.firstname) = UPPER(?1)
四、分页和复杂查询
  • 分页

    Repository自带的方法就包含分页,所以只要构造参数传递过去就可以。下面使用PageRequest构造

    Page<Role> rolePage = roleRepository.findAll(PageRequest.of(0, 2));
    
    logger.info("nums:{}, pages:{}, list:{}", rolePage.getTotalElements(), rolePage.getTotalPages(), rolePage.get().collect(Collectors.toList()));
    
  • Criteria查询

    在jpa中,通过Specification这个接口实现Criteria查询

    • 单表多条件查询

      Specification<User> specification =  (root, query, criteriaBuilder) -> {
      
      	Predicate namePred = criteriaBuilder.like(root.get("username").as(String.class),"%赵%");
      	Predicate datePred = criteriaBuilder.between(root.get("lastLoginTime").as(LocalDateTime.class), LocalDateTime.of(2020, 2, 2, 2, 2, 2), LocalDateTime.now());
      
      	return criteriaBuilder.or(namePred, datePred);
      };
      logger.info("名字带赵或者上次登录时间在2020-2-2 2:2:2到现在的用户:{}", userRepository.findAll(specification));
      
    • 多表连接查询

      Specification specification = (root, query, criteriaBuilder) -> {
      	Join role = root.join("roles", JoinType.LEFT);
      	Predicate p1 = criteriaBuilder.equal(role.get("name"), "管理员");
      	Predicate p2 = criteriaBuilder.equal(root.get("username"), "张三");
      	return criteriaBuilder.and(p1, p2);
      };
      
      List all = userRepository.findAll(specification);
      logger.info("名字叫张三的管理员:{}", all);
      
  • Example查询

    Query by Example (QBE) 是一种简单、用户友好的查询技术。一般用来对字符串进行精确的查询,不能对时间、数字进行大于小于等查询。它的优点在于可以忽略某些属性、能够对基本类型处理、大小写处理等。

    User user = User.builder().username("赵").build();
    ExampleMatcher exampleMatcher = ExampleMatcher.matching()
    		.withMatcher("username", matcher -> matcher.contains());
    
    List<User> allUser = userRepository.findAll(Example.of(user, exampleMatcher));
    logger.info("users:{}", allUser);
    
  • Querydsl查询

    pom.xml 引入依赖 加入插件,用于生成查询实例Q类,具体使用可以查看官方文档

    <dependencies>
            <dependency>
                <groupId>com.querydslgroupId>
                <artifactId>querydsl-jpaartifactId>
            dependency>
            <dependency>
                <groupId>com.querydslgroupId>
                <artifactId>querydsl-aptartifactId>
            dependency>
    dependencies>
    <plugins>
            <plugin> 
                    <groupId>com.mysema.mavengroupId>
                    <artifactId>apt-maven-pluginartifactId>
                    <version>1.1.3version>
                    <executions>
                        <execution>
                            <phase>generate-sourcesphase>
                            <goals>
                                <goal>processgoal>
                            goals>
                            <configuration>
                                <outputDirectory>target/generated-sources/javaoutputDirectory>
                                <processor>com.querydsl.apt.jpa.JPAAnnotationProcessorprocessor>
                            configuration>
                        execution>
                    executions>
                plugin>
    plugins>
    

    实体Bean配置了@Entity被检测到之后,就会在target的子目录中自动生成一个Q+实体名称的类,这个类对我们使用QueryDSL非常重要,正是因为它,我们才使得QueryDSL能够构建类型安全的查询

    如果没有生成,需要手动mvn compile

    @Bean
    public JPAQueryFactory jpaQueryFactory(EntityManager entityManager){
    	return new JPAQueryFactory(entityManager);
    }
    
    
    @Test
    void testQueryDsl() {
        QUser qUser = QUser.user;
        QRole qRole = QRole.role;
    
        List<User> users = jpaQueryFactory.selectFrom(qUser).where(qUser.username.eq("张三")).fetch();
        logger.info("{}", users);
    
        List<User> userList = jpaQueryFactory.selectFrom(qUser).leftJoin(qUser.roles, qRole).where(qRole.name.eq("管理员")).fetch();
        logger.info("{}", userList);
        
        com.querydsl.core.types.Predicate p = qUser.birthdate.between(LocalDate.of(2000, 1, 1), LocalDate.of(2022, 1, 1)).or(qUser.username.contains("赵"));
    
    	Iterable<User> all = userRepository.findAll(p);
    	logger.info("{}", Lists.newArrayList(all));
    }
    
五、运行错误和解决方法
  1. 出现org.hibernate.tool.schema.spi.CommandAcceptanceException:Error executing DDL via JDBC Statement错误

    需要加上方言

    spring:
      jpa:
        properties:
          hibernate:
            dialect: org.hibernate.dialect.MySQL57Dialect # 之前使用的是MySQL5Dialect,会造成下面的问题,需要改成这个
    
  2. 在测试方法上使用@Transactional回滚数据失败,原因是之前添加的方言是org.hibernate.dialect.MySQL5Dialect,jpa自动生成的表示MyISAM表,不支持事务和外键,需要使用InnoDB引擎,解决方法,删除原来的表,修改dialect如上,重新执行代码

参考
  • https://docs.spring.io/spring-data/jpa/docs/current/reference/html/
  • https://hibernate.org/orm/documentation/5.5/
  • https://docs.oracle.com/javaee/6/tutorial/doc/bnbpz.html
  • https://www.jianshu.com/p/69dcb1b85bbb

欢迎分享,转载请注明来源:内存溢出

原文地址:https://www.54852.com/langs/905651.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-15
下一篇2022-05-15

发表评论

登录后才能评论

评论列表(0条)

    保存