IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> SpringBoot学习小结之JPA -> 正文阅读

[Java知识库]SpringBoot学习小结之JPA

前言

? 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.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.6.7</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>30.0-jre</version>
        </dependency>
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-jpa</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.querydsl</groupId>
            <artifactId>querydsl-apt</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>

            <plugin>
                <groupId>com.mysema.maven</groupId>
                <artifactId>apt-maven-plugin</artifactId>
                <version>1.1.3</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>process</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>target/generated-sources/java</outputDirectory>
                            <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
                        </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种类型,具体可以查看官方文档

三、主要接口

?interface?
Repository
?interface?
CrudRepository
?interface?
PagingAndSortingRepository
?interface?
JpaRepository
?interface?
QueryByExampleExecutor
?interface?
JpaRepositoryImplementation
?interface?
JpaSpecificationExecutor
SimpleJpaRepository

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<Age> ages)… where x.age in ?1
NotInfindByAgeNotIn(Collection<Age> 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.querydsl</groupId>
                <artifactId>querydsl-jpa</artifactId>
            </dependency>
            <dependency>
                <groupId>com.querydsl</groupId>
                <artifactId>querydsl-apt</artifactId>
            </dependency>
    </dependencies>
    <plugins>
            <plugin> <!--因为QueryDsl是类型安全的,所以还需要加上Maven APT plugin,使用 APT 自动生成Q类:-->
                    <groupId>com.mysema.maven</groupId>
                    <artifactId>apt-maven-plugin</artifactId>
                    <version>1.1.3</version>
                    <executions>
                        <execution>
                            <phase>generate-sources</phase>
                            <goals>
                                <goal>process</goal>
                            </goals>
                            <configuration>
                                <outputDirectory>target/generated-sources/java</outputDirectory>
                                <processor>com.querydsl.apt.jpa.JPAAnnotationProcessor</processor>
                            </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如上,重新执行代码

参考

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-05-11 16:17:11  更:2022-05-11 16:19:55 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 23:31:53-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码