栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

SpringBoot学习小结之JPA

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

SpringBoot学习小结之JPA

前言

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

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

一、基本使用

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


    org.springframework.boot
    spring-boot-starter-parent
    2.6.7
     



        
            org.springframework.boot
            spring-boot-starter-data-jpa
        
        
            org.springframework.boot
            spring-boot-starter-web
        

        
            mysql
            mysql-connector-java
            runtime
        
        
            org.projectlombok
            lombok
            true
        
        
            com.google.guava
            guava
            30.0-jre
        
        
            com.querydsl
            querydsl-jpa
            provided
        
        
            com.querydsl
            querydsl-apt
            provided
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    
    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                    
                        
                            org.projectlombok
                            lombok
                        
                    
                
            

            
                com.mysema.maven
                apt-maven-plugin
                1.1.3
                
                    
                        
                            process
                        
                        
                            target/generated-sources/java
                            com.querydsl.apt.jpa.JPAAnnotationProcessor
                        
                    
                
            
        
    

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 extends JpaRepository, JpaSpecificationExecutor, QuerydslPredicateExecutor {
}

RoleRepository.java

public interface RoleRepository extends BaseRepository {
    List findRolesByNameStartingWith(String name);
}

UserRepository.java

public interface UserRepository extends BaseRepository {
}

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 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 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 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 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 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 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 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 users = jpaQueryFactory.selectFrom(qUser).where(qUser.username.eq("张三")).fetch();
        assertThat(users.size()).isEqualTo(1);

        List 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 all = userRepository.findAll(p);
        List userList2 = Lists.newArrayList(all);

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

    }

    @Test
    void testRoleQuery() {
        List 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 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 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 allUser = userRepository.findAll(Example.of(user, exampleMatcher));
    logger.info("users:{}", allUser);
    
  • Querydsl查询

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

    
            
                com.querydsl
                querydsl-jpa
            
            
                com.querydsl
                querydsl-apt
            
    
    
             
                    com.mysema.maven
                    apt-maven-plugin
                    1.1.3
                    
                        
                            generate-sources
                            
                                process
                            
                            
                                target/generated-sources/java
                                com.querydsl.apt.jpa.JPAAnnotationProcessor
                            
                        
                    
                
    
    

    实体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 users = jpaQueryFactory.selectFrom(qUser).where(qUser.username.eq("张三")).fetch();
        logger.info("{}", users);
    
        List 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 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
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/874174.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号