关于jpa:JPA大坑-无法像mybaits一样在currentat字段自动生成创建时间

JPA大坑 ,无奈像mybaits一样在current_at字段,主动生成创立工夫mybatis jpa下实体类的保留 mybatis在mybatis我的项目中,咱们个别会应用它的插件plus以裁减它的根本查问性能。另一方面,在阿里巴巴开发手册的标准中也提到,在数据库表创立的时候,个别会有一个create_time和update_time字段,它们的建表语句往往如下: 'create_time' timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,'update_time' timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP创建者则是心愿在行insert的时候会主动生成create_time,同时在其它行进行批改后,执行update操作会自动更新update_time字段。这样简化了开发,只有存储业务字段就能够了,开发者不必管工夫字段的生成。 jpa下实体类的保留新我的项目应用jpa,我也想当然的在表下create_at字段设置 NOT NULL DEFAULT CURRENT_TIMESTAMP,后果调save(entity)保留实体类的时候,发现基本没有像我想的那样在创立工夫字段保留insert的工夫,所有生成工夫字段都为null…打印sql语句才发现,原先调jpa的save办法,默认jpa会把所有字段都insert,为空的话就会本人设置null insert到表中,导致字段设置NOT NULL DEFAULT CURRENT_TIMESTAMP生效,所有的创立工夫字段都得本人set以后工夫这个与业务无关的字段,忘了的话就会报错了,效率低容易出错。最初发现还得在相应的实体类字段写如下正文才能够实现与mybaits雷同的成果,交由数据库去实现创立工夫的创立。 @Column(name = "create_at",insertable = false,updatable = false,columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP") private Date createAt; @Column(name = "update_at",insertable = false,updatable = false,columnDefinition="TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP") private Date updateAt;columnDefinition和前面的设置,就等于在数据库中设置DEFAULT CURRENT_TIMESTAMP 和ON UPDATE CURRENT_TIMESTAMP了,这样设置就会让createTime成为创立工夫,updateTime成为更新工夫,数据库会主动去保护他。 然而还有一个问题,jpa调用save办法时,会把这两个字段笼罩掉。所以这里须要insertable = false,updatable = false,这样jpa更新插入时就不会去更新这个字段了,而是齐全由数据库保护。参考文档:https://blog.csdn.net/weixin_30666401/article/details/96759783

January 31, 2022 · 1 min · jiezi

关于jpa:JPA-实体脏检查与存储同步Dirty-Flush

引言问题起源于对新我的项目-数字外围的代码审查,在审阅账户模块后,发现补录、更新等接口没有调用JPA的仓库save办法进行数据长久化,但更新仍然失效,随查阅材料文献,开启了对本议题的探索。 指标:没有调用save办法,更新是怎么失效的? 试一试在查阅大量材料后,理解到与JPA长久化上下文Persistence Context无关,一起试试吧。 试验筹备初始化spring-boot我的项目,依赖spring-data-jpa,并开启spring-data的show-sql配置以便调试。 spring: jpa: show-sql: true建设客户信息实体: /** * 客户信息表 */@Entity@Table(name = "CUSTOMER")public class Customer { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; /** * 客户姓名 */ private String name; /** * 客户手机号 */ private String phone; @Override public String toString() { return "Customer{" + "id=" + id + ", name='" + name + '\'' + ", phone='" + phone + '\'' + '}'; }}配置DataJpaTest启用JPA测试环境,不启动整个spring-context,能够缩小单元测试执行耗时。 /** * 客户信息仓库测试 */@DataJpaTest // 主动配置JPA测试@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) // 不启用内嵌数据库代替public class CustomerRepositoryTest { private static final Logger logger = LoggerFactory.getLogger(CustomerRepositoryTest.class);}复现更新场景更新办法如下,构建一条Hello Kitty!测试数据,并对保留后的实体信息进行批改,但不调用save。 ...

January 15, 2022 · 4 min · jiezi

关于jpa:spring-data-jpa使用native-sql进行分页查询时需要使用countQuery

问题发现:应用data jpa的native sql进行分页查问时,发现一个sql grammar语法报错,再三查看确认本人的sql写的没有问题,sql大抵为 select * from (select * from table1 where ) as table2 where的一个子查问构造,报错为 from (select *) from table1 where ) as table2 where左近有语法错误,比照发现多了一个)右括号。最初发现是分页查问的count查问导致的。 题外话:在应用spring data jpa的@Query的native sql进行分页查问时,能够在办法最初一个参数传入pageable做分页查问,当然也能够在native sql中应用limit offset做分页查问,第一种形式会转换为第二种形式执行。 但分页查问不仅仅返回查问分页的数据,同样会返回符合条件的总个数,也就是totalElements以及totalPages。那么肯定会有一个count查问来干这个事,如果你没写count查问(我就没写),jpa会本人帮你生成一个,但jpa生成的不肯定是你想要的,问题发现中的语法错误就是jpa帮咱们生成的count查问。正确的是: select count(*) from (select * from table1 where ) as table2 where生成的是: select count(* from (select *) from table1 where ) as table2 wherejpa生成的count查问右括号匹配到了第二个*,也就导致了语法错误。。。。。 拓展:对于count的执行问题jpa有逻辑如下:SimpleJpaRepository(crudRepository的默认实现)下的咱们能够看到第一张图中在做分页是传入的第三个参数就是计数查问,而在第二张图中当查问到的数据是少于pageSize时,也就是总数能够在以后查到的第一页算出时,是不须要去执行后续的count查问的,也算是个简略的优化。官网注解的话是: The construction of {@link Page} omits a count query if the total can be determined based on the result size and {@link Pageable}.总结:而在应用办法名规定或者qbc进行分页查问时是不会呈现上述问题的,查问sql和计数sql都是统一生成的。而应用natvie sql时jpa会依据你写的sql语句改写count(看起来只是做了一些简略的字符串匹配),于是呈现了上述问题。在应用native sql进行分页查问时记得应用countQuery ...

November 25, 2021 · 1 min · jiezi

关于jpa:Spring-Data-JPA-报-HOUROFDAY-0-1异常的解决过程和方案

在进行数据查问时,控制台报了Caused by: com.mysql.cj.exceptions.WrongArgumentException: HOUR_OF_DAY: 0 -> 1异样,查问得悉:这是因为查mysql库,转换类型为datetime类型的字段引起的。 网上的解决方案有多种,大多数都是通过设置时区来解决的,但遗憾的是通过测试我发现即便在将数据跑在时区正确的数据库上,在执行起来我这依然出错。 最初发现我的问题呈现在夏令时上。 夏令时记得小时候过过几个夏令时,大略的意思就是在某一天把表调快1个小时,而后再到某一天把表再调慢1个小时。这间接造成的问题的是:xxxx年xx月xx日会对应上两个工夫戳。比方咱们假如把表调慢的那一天是2021年10月4日的12点。具体的操作是过后钟第一次通过2021年10月4日12点时,咱们把表调到2021年10月4日11点。所以在2021年10月4日11点至12点,咱们会从新过一次。 对于工夫这块,已经回达到一个工夫戳为负的问题,也有那么点意思:https://segmentfault.com/q/1010000038248983,赶趣味的能够看看。 那么问题来了,比咱们记录用户的出世工夫,准确到分钟。如果这个人录入的是2021年10月4日11点20分,那咱们的零碎没有方法来精确的判断这个工夫是第一个11点20分,还是过1小时后的第二个11点20分。 夏令时,还给咱们带来的另一个问题。有些工夫是对应不上工夫戳的。再比方咱们设置在2021年5月1日0时,将表调快1时,则在历史上不会呈现2021年5月1日0时至1时的工夫,所以如果咱们统计出世工夫点,用户写的是:2021年5月1日0时30分,则该数据必须是个假数据。 排查夏令时讲完后,咱们讲下排查过程。其实并不是所有的数据在查问时,都会报这种异样,所以要把那个非凡的点找进去,这里给一种最笨的展现办法: boolean last = false; int page = 0; Pageable pageable = PageRequest.of(page, 1); while (!last) { try { Page<Resident> residents = this.residentRepository.findAll(specification, pageable); page++; pageable = PageRequest.of(page, 1); last = residents.isLast(); } catch (Exception e) { last = true; e.printStackTrace(); this.logger.info("当前页" + pageable.getPageNumber()); } }最终控制台打印信息:2021-11-04 13:25:38.562 INFO 4226 --- [nio-8081-exec-7] c.y.s.service.ResidentServiceImpl : 当前页1089 而后咱们去数据表中把这条记录查出来: ...

November 4, 2021 · 1 min · jiezi

关于jpa:SpringBoot-整合-Spring-Data-JPA

学习应用 Spring Data JPAJPA 简介JPA(Java Persistence API)是 Java 长久化 API,是 Sun 公司提出的基于 ORM 的 Java 长久化标准。 ORM(Object Relational Mapping)的全称是对象关系映射,支流 Java ORM 框架有 Mybatis,Hibernate 等。Spring Data JPASpring Data JPA 是 Spring Data 框架的一个模块,可简化 JPA 的实现。此外,Spring Data JPA 还能够帮忙咱们简化长久层的开发。对于简略查问,Spring Data JPA 能够依据办法名称进行解析,并主动生成查问语句进行查问;对于简单查问,Spring Data JPA 同样反对原生的 SQL。 Spring Data JPA 实际创立 SpringBoot 我的项目,利用 JPA 实现简略 CRUD。 1. 引入依赖POM 文件如下: <?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.5.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <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>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </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> </plugins> </build></project>2. 配置 JPA 和 MySQLapplication.yml 配置文件如下: ...

October 14, 2021 · 3 min · jiezi

关于jpa:JPA一对多多对多配置

单向一对多@Data@Entity@Table(name = "banner")public class Banner { @Id private Integer id; private String name; @OneToMany(fetch = FetchType.LAZY) @JoinColumn(name = "bannerId", referencedColumnName = "id") private List<BannerItem> items;}下面为双方配置,@JoinColumn注解外面name示意多方中配置的外键名称,referencedColumnName为外键对应的双方主键。 @Data@Entity@Table(name = "banner_item")public class BannerItem { @Id private Integer id; private String img; private Integer bannerId; private String name;}下面为双方配置,没有什么非凡的。 双向一对多@Data@Entity@Table(name = "banner")public class Banner { @Id private Integer id; private String name; @OneToMany(fetch = FetchType.LAZY, mappedBy="banner") private List<BannerItem> items;}双向时双方不须要@JoinColumn注解,须要在@OneToMany上增加mappedBy属性指明多方的映射属性。 @Data@Entity@Table(name = "banner_item")public class BannerItem { @Id private Integer id; private String img; private Integer bannerId; private String name; @ManyToOne @JoinColumn(name="bannerId") private Banner banner;}多方的注解应用@ManyToOne,标注在映射双方的属性上,同时须要加上@JoinColumn注解指明外键。 ...

September 9, 2021 · 1 min · jiezi

关于jpa:如何使用JPA的UUID主键生成策略

数据实体类@Entity@Table(name = "ip_user")@GenericGenerator(name = "jpa-uuid", strategy = "uuid") // name = "system-uuid"也能够public class User implements Serializable { @Id@GeneratedValue(generator = "jpa-uuid") //system-uuid同样@Column(length = 32)private String userId;...} 留神@GenericGenerator(name = "jpa-uuid", strategy = "uuid") 和 @GeneratedValue(generator = "jpa-uuid") 两个注解是生成策略外围注解。 留神@GenericGenerator(name = "system-uuid", strategy = "uuid") 和 @GeneratedValue(generator = "system-uuid") 两个注解是生成策略外围注解。 数据库字段执行save办法后不须要给user.id字段设置值,jpa会主动生成uuid并作为它的主键增加到表中。

April 22, 2021 · 1 min · jiezi

关于jpa:Spring-Data-JPA中的mappedBy

JPA,在@OneToMany里退出mappedBy属性防止生成两头表 mappedBy 单向关系不须要设置该属性,双向关系必须设置,防止单方都建设外键字段。数据库中1对多的关系,关联关系总是被多方保护的即外键建在多方,咱们在双方对象的@OneToMany(mappedby="")把关系的保护交给多方对象的属性去保护关系。对于mappedBy温习下:a) 只有OneToOne,OneToMany,ManyToMany上才有mappedBy属性,ManyToOne不存在该属性;b) mappedBy标签肯定是定义在the owned side(被领有方的),他指向theowning side(领有方);c) 关系的领有方负责关系的保护,在领有方建设外键。所以用到@JoinColumnd)mappedBy跟JoinColumn/JoinTable总是处于互斥的一方 这里的保护关联关系,拿多对多来说就是两头表,在不设置cascade的状况下,两头表由负责保护关联关系的一方保护 参考链接:https://www.cnblogs.com/hyl82...http://www.mamicode.com/info-...https://www.cnblogs.com/power...

December 11, 2020 · 1 min · jiezi

关于jpa:JPA中使用Sort排序时遇到的问题

JPA中应用Sort排序时遇到的问题org.springframework.data.jpa.repository.JpaRepository中能够应用Sort定义排序规定,但在应用时我发现了一些小问题 失常基于Pageable中的Sort字段排序 Sort sort = new Sort(Direction.ASC, "seqNum");Pageable pageable = new PageRequest(0, size, sort);然而,问题来了他报错了!!!起因是这两个类的构造方法被定义为了公有或者爱护的办法我从网上查了好多相干材料,但他们用的时候貌似都能够间接实例化进去,甚至官网上也是这么用的,但我却不能这样用 (好气)为了解决这个问题,我看了下这两个类的源码,好在类外面定义了相干的静态方法可能应用所以能够通过调用这些写好的静态方法而不必实例化就能达到雷同的成果 Sort sort = Sort.by(Sort.Direction.DESC,"blogs.size");Pageable pageable = PageRequest.of(0,size,sort);PS:起因是springboot版本太新导致的

December 1, 2020 · 1 min · jiezi

关于jpa:Spring-Data-JPA使用看这一篇就够了

简介首先理解Spring Date JPA是什么? SpringData:其实SpringData就是Spring提供了一个操作数据的框架。而SpringData JPA只是SpringData框架下的一个基于JPA规范操作数据的模块。SpringData JPA:基于JPA的规范数据进行操作。简化操作长久层的代码。只须要编写接口就能够。 JPA是Spring Data下的子项目,JPA是Java Persistence API的简称,中文名为Java长久层API,是JDK 5.0注解或XML形容对象-关系表的映射关系,并将运行期的实体对象长久化到数据库中 你能够了解为JPA和Mybatis是起雷同作用的,都是长久层的框架,然而因为当初Mybatis的广泛应用,当初理解和应用JPA的人较少. 但在我应用的过程中,也发现其一些劣势. 整合1.导入jar包<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId></dependency>2.yml配置文件spring: datasource: url: jdbc:mysql://localhost:3306/mytest type: com.alibaba.druid.pool.DruidDataSource username: root password: root driver-class-name: com.mysql.jdbc.Driver //驱动 jpa: hibernate: ddl-auto: update //自动更新 show-sql: true //日志中显示sql语句这里留神:jpa:hibernate:ddl-auto: update是hibernate的配置属性,其次要作用是:主动创立、更新、验证数据库表构造。该参数的几种配置如下:1.·create:每次加载hibernate时都会删除上一次的生成的表,而后依据你的model类再从新来生成新表,哪怕两次没有任何扭转也要这样执行,这就是导致数据库表数据失落的一个重要起因。2.·create-drop:每次加载hibernate时依据model类生成表,然而sessionFactory一敞开,表就主动删除。3.·update:最罕用的属性,第一次加载hibernate时依据model类会主动建设起表的构造(前提是先建设好数据库),当前加载hibernate时依据model类自动更新表构造,即便表构造扭转了但表中的行依然存在不会删除以前的行。要留神的是当部署到服务器后,表构造是不会被马上建设起来的,是要等利用第一次运行起来后才会。4.·validate:每次加载hibernate时,验证创立数据库表构造,只会和数据库中的表进行比拟,不会创立新表,然而会插入新值。我在首次创立时会设为create,创立好后改为validate. 3.实体类既然上边的参数能够帮忙咱们主动的去通过实体类来创立保护表,那么实体类该怎么写呢,又是怎么建设与表的映射 简略的创立一个实体类:get/set办法由注解实现 @Entity@Getter@Setter@Table(name = "person")public class Person { @Id @GeneratedValue private Long id; @Column(name = "name", length = 20) private String name; @Column(name = "agee", length = 4) private int age;}创立好实体类并标注好注解后启动主启动类,应该就会在你配置的数据库中主动生成表. ...

November 7, 2020 · 3 min · jiezi

使用SpringDataJPA的Query注解完成动态条件分页查询

写作原因之前在学校都是做前端,但是最后找了个Java后端的工作,框架什么的基本没用过,所以工作中遇到了很多问题,所以决定记录下来工作中遇到的问题,记录成长的点滴。正文公司使用的是现在流行的SpringBoot,数据库方面使用的是SpringData+JPA+Hibernate。这几天用的最多的就是用JPA进行查询了,简单的查询很简单,网上查一查就有一堆方案,直到遇到分页查询的时候出了问题。 网上查到的大多都是使用EntityManager通过人工拼接sql来查询的,但是今天从导师那里学到了一手,在Repository中使用@Query注解即可。代码如下 public interface StudentRepository extends JpaRepository<Student,Integer> { @Query( value = "SELECT * FROM student" + " WHERE (age = ?1 OR ?1 IS NULL)" + " WHERE (id = ?2 OR ?2 IS NULL)" + " WHERE (first_name = ?3 OR ?3 IS NULL)" + " ORDER BY ?#{#pageable}", countQuery = "SELECT COUNT(1) FROM " + "SELECT * FROM student" + " WHERE (age = ?1 OR ?1 IS NULL)" + " WHERE (id = ?2 OR ?2 IS NULL)" + " WHERE (first_name = ?3 OR ?3 IS NULL)" + " ORDER BY ?#{#pageable}", nativeQuery = true ) Page<Student> findByAgeAndIdAndFirstName(Integer age, Integer id, String firstName, Pageable pageable);}最后的Pageable可以使用PageRequest来创建。 ...

July 17, 2019 · 1 min · jiezi

SpringBoot20-基础案例09集成JPA持久层框架简化数据库操作

本文源码GitHub:知了一笑https://github.com/cicadasmile/spring-boot-base一、JAP框架简介JPA(Java Persistence API)意即Java持久化API,是Sun官方在JDK5.0后提出的Java持久化规范。主要是为了简化持久层开发以及整合ORM技术,结束Hibernate、TopLink、JDO等ORM框架各自为营的局面。JPA是在吸收现有ORM框架的基础上发展而来,易于使用,伸缩性强。 二、与SpringBoot2.0整合1、核心依赖<!-- JPA框架 --><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId></dependency>2、配置文件spring: application: name: node09-boot-jpa datasource: url: jdbc:mysql://localhost:3306/data_jpa?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true username: root password: root driver-class-name: com.mysql.jdbc.Driver jpa: hibernate: ddl-auto: update show-sql: trueddl-auto几种配置说明1)create每次加载hibernate时都删除上一次的生成的表,然后根据bean类重新来生成新表,容易导致数据丢失,(建议首次创建时使用)。2)create-drop每次加载hibernate时根据bean类生成表,但是sessionFactory一关闭,表就自动删除。3)update第一次加载hibernate时根据bean类会自动建立起表的结构,以后加载hibernate时根据bean类自动更新表结构,即使表结构改变了但表中的行仍然存在不会删除以前的行。4)validate每次加载hibernate时,验证创建数据库表结构,只会和数据库中的表进行比较,不会创建新表,但是会插入新值。 3、实体类对象就是根据这个对象生成的表结构。 @Table(name = "t_user")@Entitypublic class User { @Id @GeneratedValue private Integer id; @Column private String name; @Column private Integer age; // 省略 GET SET}4、JPA框架的用法定义对象的操作的接口,继承JpaRepository核心接口。 import com.boot.jpa.entity.User;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.data.jpa.repository.Query;import org.springframework.data.repository.query.Param;import org.springframework.stereotype.Repository;@Repositorypublic interface UserRepository extends JpaRepository<User,Integer> { // 但条件查询 User findByAge(Integer age); // 多条件查询 User findByNameAndAge(String name, Integer age); // 自定义查询 @Query("from User u where u.name=:name") User findSql(@Param("name") String name);}5、封装一个服务层逻辑import com.boot.jpa.entity.User;import com.boot.jpa.repository.UserRepository;import org.springframework.stereotype.Service;import javax.annotation.Resource;@Servicepublic class UserService { @Resource private UserRepository userRepository ; // 保存 public void addUser (User user){ userRepository.save(user) ; } // 根据年龄查询 public User findByAge (Integer age){ return userRepository.findByAge(age) ; } // 多条件查询 public User findByNameAndAge (String name, Integer age){ return userRepository.findByNameAndAge(name,age) ; } // 自定义SQL查询 public User findSql (String name){ return userRepository.findSql(name) ; } // 根据ID修改 public void update (User user){ userRepository.save(user) ; } //根据id删除一条数据 public void deleteStudentById(Integer id){ userRepository.deleteById(id); }}三、测试代码块import com.boot.jpa.JpaApplication;import com.boot.jpa.entity.User;import com.boot.jpa.service.UserService;import org.junit.Test;import org.junit.runner.RunWith;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;import javax.annotation.Resource;@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes = JpaApplication.class)public class UserJpaTest { @Resource private UserService userService ; @Test public void addUser (){ User user = new User() ; user.setName("知了一笑"); user.setAge(22); userService.addUser(user); User user1 = new User() ; user1.setName("cicada"); user1.setAge(23); userService.addUser(user1); } @Test public void findByAge (){ Integer age = 22 ; // User{id=3, name='知了一笑', age=22} System.out.println(userService.findByAge(age)); } @Test public void findByNameAndAge (){ System.out.println(userService.findByNameAndAge("cicada",23)); } @Test public void findSql (){ // User{id=4, name='cicada', age=23} System.out.println(userService.findSql("cicada")); } @Test public void update (){ User user = new User() ; // 如果这个主键不存在,会以主键自增的方式新增入库 user.setId(3); user.setName("哈哈一笑"); user.setAge(25); userService.update(user) ; } @Test public void deleteStudentById (){ userService.deleteStudentById(5) ; }}四、源代码地址GitHub地址:知了一笑https://github.com/cicadasmile/spring-boot-base码云地址:知了一笑https://gitee.com/cicadasmile/spring-boot-base ...

July 12, 2019 · 2 min · jiezi

SpringBoot系列教程JPA之delete使用姿势详解

原文: 190702-SpringBoot系列教程JPA之delete使用姿势详解 常见db中的四个操作curd,前面的几篇博文分别介绍了insert,update,接下来我们看下delete的使用姿势,通过JPA可以怎样删除数据 一般来讲是不建议物理删除(直接从表中删除记录)数据的,在如今数据就是钱的时代,更常见的做法是在表中添加一个表示状态的字段,然后通过修改这个字段来表示记录是否有效,从而实现逻辑删除;这么做的原因如下 物理删除,如果出问题恢复比较麻烦无法保证代码一定准确,在出问题的时候,删错了数据,那就gg了删除数据,会导致重建索引Innodb数据库对于已经删除的数据只是标记为删除,并不真正释放所占用的磁盘空间,这就导致InnoDB数据库文件不断增长,也会导致表碎片逻辑删除,保留数据,方便后续针对数据的挖掘或者分析<!-- more --> I. 环境准备在开始之前,当然得先准备好基础环境,如安装测试使用mysql,创建SpringBoot项目工程,设置好配置信息等,关于搭建项目的详情可以参考前一篇文章 190612-SpringBoot系列教程JPA之基础环境搭建下面简单的看一下演示添加记录的过程中,需要的配置 1. 表准备沿用前一篇的表,结构如下 CREATE TABLE `money` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL DEFAULT '' COMMENT '用户名', `money` int(26) NOT NULL DEFAULT '0' COMMENT '钱', `is_deleted` tinyint(1) NOT NULL DEFAULT '0', `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `name` (`name`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;2. 项目配置配置信息,与之前有一点点区别,我们新增了更详细的日志打印;本篇主要目标集中在添加记录的使用姿势,对于配置说明,后面单独进行说明 ...

July 4, 2019 · 3 min · jiezi

SpringBoot系列教程JPA之update使用姿势

原文: 190623-SpringBoot系列教程JPA之update使用姿势上面两篇博文拉开了jpa使用姿势的面纱一角,接下来我们继续往下扯,数据插入db之后,并不是说就一层不变了,就好比我在银行开了户,当然是准备往里面存钱了,有存就有取(特别是当下银行利率这么低还不如买比特币屯着,截止19年6月22日,btc已经突破1.1w$,可惜没钱买????)这就是我们今天的主题,数据更新--update的使用姿势 <!-- more --> 通过本篇博文,您至少可以选到 save() 直接根据id来修改记录利用jpl 实现查询修改的使用姿势初识事物的神秘面纱I. 环境准备在开始之前,当然得先准备好基础环境,如安装测试使用mysql,创建SpringBoot项目工程,设置好配置信息等,关于搭建项目的详情可以参考前一篇文章 190612-SpringBoot系列教程JPA之基础环境搭建190614-SpringBoot系列教程JPA之新增记录使用姿势下面简单的看一下演示添加记录的过程中,需要的配置 1. 表准备沿用前一篇的表,结构如下 CREATE TABLE `money` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL DEFAULT '' COMMENT '用户名', `money` int(26) NOT NULL DEFAULT '0' COMMENT '钱', `is_deleted` tinyint(1) NOT NULL DEFAULT '0', `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `name` (`name`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;2. 项目配置配置信息,与之前有一点点区别,我们新增了更详细的日志打印;本篇主要目标集中在添加记录的使用姿势,对于配置说明,后面单独进行说明 ...

June 23, 2019 · 2 min · jiezi

SpringBoot系列教程JPA之新增记录使用姿势

SpringBoot系列教程JPA之新增记录使用姿势上一篇文章介绍了如何快速的搭建一个JPA的项目环境,并给出了一个简单的演示demo,接下来我们开始业务教程,也就是我们常说的CURD,接下来进入第一篇,如何添加数据 通过本篇文章,你可以get到以下技能点 POJO对象如何与表关联如何向DB中添加单条记录如何批量向DB中添加记录save 与 saveAndFlush的区别<!-- more --> I. 环境准备实际开始之前,需要先走一些必要的操作,如安装测试使用mysql,创建SpringBoot项目工程,设置好配置信息等,关于搭建项目的详情可以参考前一篇文章 190612-SpringBoot系列教程JPA之基础环境搭建 下面简单的看一下演示添加记录的过程中,需要的配置 1. 表准备沿用前一篇的表,结构如下 CREATE TABLE `money` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(20) NOT NULL DEFAULT '' COMMENT '用户名', `money` int(26) NOT NULL DEFAULT '0' COMMENT '钱', `is_deleted` tinyint(1) NOT NULL DEFAULT '0', `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `name` (`name`)) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;2. 项目配置配置信息,与之前有一点点区别,我们新增了更详细的日志打印;本篇主要目标集中在添加记录的使用姿势,对于配置说明,后面单独进行说明 ...

June 16, 2019 · 3 min · jiezi

SpringBoot系列教程JPA之基础环境搭建

JPA(Java Persistence API)Java持久化API,是 Java 持久化的标准规范,Hibernate是持久化规范的技术实现,而Spring Data JPA是在 Hibernate 基础上封装的一款框架。JPA作为标准,实际上并没有说局限于某个固定的数据源,事实上mysql,mongo, solr都是ok的。接下来我们将介绍下springboot结合jpa 来实现mysql的curd以及更加复杂一点的sql支持 <!-- more --> jpa系列教程将包含以下几块 环境搭建基础的插入、修改、删除数据的使用姿势基础的单表查询,如(>, <, = , in, like, between),分页,排序等多表关联查询事物使用本篇为开始第一篇,先搭建一个可以愉快玩耍的jpa项目 I. 环境搭建我们选择的数据库为mysql,所以有必要先安装一下,这里跳过mysql的安装教程,直接进入springboot项目的搭建 1. pom依赖我们这里选择的是2.0.4.RELEASE版本进行演示 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.4.RELEASE</version> <relativePath/> <!-- lookup parent from update --></parent><properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring-cloud.version>Finchley.RELEASE</spring-cloud.version> <java.version>1.8</java.version></properties><dependencies> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.45</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </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></dependencies><build> <pluginManagement> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </pluginManagement></build><repositories> <repository> <id>spring-milestones</id> <name>Spring Milestones</name> <url>https://repo.spring.io/milestone</url> <snapshots> <enabled>false</enabled> </snapshots> </repository></repositories>上面的pom依赖中,关键的是下面两个, 第一个引入的是jpa相关包,后面那个则是mysql的连接依赖,相当于指定操作mysql数据库 ...

June 16, 2019 · 2 min · jiezi

神奇的session-in-jpa

引言再回顾一下问题场景: Iterable<Teacher> teachers = teacherRepository.findAll();for (Teacher teacher : teachers) { logger.debug("教师: " + teacher.getName()); for (Klass klass : teacher.getKlasses()) { logger.debug("班级: " + klass.getName()); for (Student student : klass.getStudents()) { logger.debug("学生: " + student.getName()); } }}在单元测试中跑这段代码,是报错的,no Session,说明执行完teacherRepository.findAll()之后,session就已经关闭了。继续执行,session已经关闭,再去数据库查教师关联的班级信息,就错了。 然而呢?把这段代码再放到Service里,写一个接口,交给浏览器去调用,却正常执行,说明session还在。 然后就一直研究为什么不好使?如果能把这个原因分析明白,以后再遇到no session错误的时候就可以一劳永逸了。 探究调试调试最简单的方法就是中断,但是咱水平还不行,也不知道JPA内部去找Hibernate怎么调用的,中断哪个方法呢? 后台发现了另一种调试的方法,JPA的源码中也是像我们开发时经常写日志的,logger.debug()什么的。 slf4j中常用的日志级别就ERROR、WARN、INFO、DEBUG四种,我们可以将JPA的日志级别设置为DEBUG级别,这样我们就可以根据日志推测到JPA内部到底是怎么执行的了。 修改日志级别logging.level.org.springframework.orm.jpa=debug修改配置文件,将JPA的日志级别设置为DEBUG。 在单元测试中执行 完整日志: 2019-06-06 11:36:40.415 DEBUG 11391 --- [ main] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly2019-06-06 11:36:40.416 DEBUG 11391 --- [ main] o.s.orm.jpa.JpaTransactionManager : Opened new EntityManager [SessionImpl(1334204880<open>)] for JPA transaction2019-06-06 11:36:40.429 DEBUG 11391 --- [ main] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@73b74615]2019-06-06 11:36:40.449 INFO 11391 --- [ main] o.h.h.i.QueryTranslatorFactoryInitiator : HHH000397: Using ASTQueryTranslatorFactoryHibernate: select teacher0_.id as id1_2_, teacher0_.name as name2_2_ from teacher teacher0_2019-06-06 11:36:40.598 DEBUG 11391 --- [ main] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit2019-06-06 11:36:40.598 DEBUG 11391 --- [ main] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(1334204880<open>)]2019-06-06 11:36:40.601 DEBUG 11391 --- [ main] o.s.orm.jpa.JpaTransactionManager : Closing JPA EntityManager [SessionImpl(1334204880<open>)] after transaction2019-06-06 11:36:40.602 DEBUG 11391 --- [ main] com.yunzhiclub.jpa.JpaApplicationTests : 教师: 张三org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.yunzhiclub.jpa.entity.Teacher.klasses, could not initialize proxy - no Session分析Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnlyOpened new EntityManager [SessionImpl(1334204880<open>)] for JPA transactionExposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@73b74615]HHH000397: Using ASTQueryTranslatorFactoryHibernate: select teacher0_.id as id1_2_, teacher0_.name as name2_2_ from teacher teacher0_Initiating transaction commitCommitting JPA transaction on EntityManager [SessionImpl(1334204880<open>)]上来是先执行了一个事务,为什么会有事务呢? ...

June 6, 2019 · 5 min · jiezi

我就是不看好jpa

知乎看到问题《SpringBoot开发使用Mybatis还是Spring Data JPA??》,顺手一答,讨论激烈。我实在搞不懂spring data jpa为啥选了hibernate作为它的实现,是“Gavin King”的裙带关系么?DAO层搞来搞去,从jdbc到hibernate,从toplink到jdo,到现在MyBatis胜出,是有原因的。 目前,一些狗屁培训公司,还有一些网络课程,包括一些洋课程,为了做项目简单,很多会使用jpa。但到了公司发现根本就不是这样,这就是理论和现实的区别。 顶着被骂的风险,我整理发出来。这可不是争论php好还是java好性质了。 忠告:精力有限,一定要先学MyBatis啊。jpa那一套,表面简单而已。以下是原始回答如果你经历过多个公司的产品迭代,尤其是复杂项目,你就会发现Spring Data JPA这种东西是多么的不讨好。说实话,Mybatis的功能有时候都嫌多,有些纯粹是画蛇添足。 jpa虽然是规范,但和hibernate这种ORM是长得比较像的(不说一些特殊场景的用法)。 Spring Data JPA 是个玩具,只适合一些简单的映射关系。也不得不提一下被人吹捧的querydsl,感觉有些自作聪明的讨巧。实在不想为了一个简单的DAO层,要学一些费力不讨好的东西。 List<Person> persons = queryFactory.selectFrom(person) .where(person.children.size().eq( JPAExpressions.select(parent.children.size().max()) .from(parent))) .fetch();看看上面的查询语句,完全不如普通SQL表达的清晰。要是紧急排查个问题,妈蛋... jpa虽然有很多好处,比如和底层的SQL无关。但我觉得Spring Data JPA有以下坏处: 1、 屏蔽了SQL的优雅,发明了一种自己的查询方式。这种查询方式并不能够覆盖所有的SQL场景。 2、 增加了代码的复杂度,需要花更多的时间来理解DAO 3、DAO操作变的特别的分散,分散到多个java文件中,或者注解中(虽然也支持XML)。如果进行一些扫描,或者优化,重构成本大 4、不支持复杂的SQL,DBA流程不好切入 Mybatis虽然也有一些问题,但你更像是在写确切的SQL。它比Spring Data JPA更加轻量级。 你只要在其他地方调试好了SQL,只需要写到配置文件里,起个名字就可以用了,少了很多烧脑的转换过程。 Mybatis完全能够应对工业级的复杂SQL,甚至存储过程(不推荐)。个人认为Mybatis依然是搞复杂了,它其中还加了一些类似if else的编程语言特性。 你的公司如果有DBA,是不允许你乱用SQL的。用Mybatis更能切入到公司的流程上。 所以我认为:玩具项目或者快速开发,使用Spring Boot JPA。反之,Mybatis是首选。 一些有用的评论你说的if else是指的SQL拼接吧,这可是MyBatis的重要功能之一,学起来一点儿也不复杂好吗? 最近也在研究持久层,可以充分利用这个jpa这个玩具,两者结合是不错的选择,jpa基本的单表操作,mybatis做复杂查询,开发效率高,降低sql维护成本,也为优化留下空间,当然这需要对spring-data-jpa做一些扩展 查询直接sql,其他的还是orm方便 mybatis主要是原生sql,对于其他没学习过jpa的开发人员而言降低了学习维护门槛,而且说真的jpa写了个锅你去追其实还是挺头疼的... mybatis-plus整合之后基本curd不用纠结了,很多对对象操作然后直接save就好。复杂场景、联表就直接用原生sql就好,至于性能问题可以用sqlAdvice优化下。 jdbc template+代码生成器,更简单更高效 jpa这玩意,写一个简单的数据库操作,就比如说单表的操作,很好用。如果是多表,那就算了吧 spring boot 推荐jpa,知道为什么吗 native=true 想用原生查询也没人拦着你啊 你好像不是很懂hibernate和jpa… 不知道你是否清楚 jpa,hibernate,spring data jpa,还有querydsl 之间的关系。 总有一天你会知道数据库优先和程序优先的区别 jpa还有一个好处,那就是帅锅啊 END来,越年轻越狂妄的家伙们,来喷我啊。 ...

June 3, 2019 · 1 min · jiezi

JPA-查询问题探究

引言最近看到了实体图相关的博客,国外老哥说JPA的实体间关联会执行多次查询,当数据量大时性能非常差。 正好实体图在之前写华软的时候就学习过,只是没学明白,没在项目中实际应用,正好借此机会学习一下。 实践出真知,建一个jpa的项目,实际测试一下jpa到底是怎么查询的。 今日才发现,Spring Boot都更新到了2.1.5。 探究实体关系最简单的教务系统模型,教师、班级、学生。 基础数据两个教师,一个教师带俩班,一个班里俩学生。 单表查询Iterable<Teacher> teachers = teacherRepository.findAll();for (Teacher teacher : teachers) { System.out.println(teacher.getName());}最简单的查询语句,findAll之后获取name字段。 Hibernate: select teacher0_.id as id1_2_, teacher0_.name as name2_2_ from teacher teacher0_简单的单表查询,查出了基础字段id和name。 OneToMany多表关联查询Iterable<Teacher> teachers = teacherRepository.findAll();for (Teacher teacher : teachers) { System.out.println(teacher.getName()); for (Klass klass : teacher.getKlasses()) { System.out.println(klass.getName()); for (Student student : klass.getStudents()) { System.out.println(student.getName()); } }}看着挺普通的一段代码,大家应该都写过,其实里面大有学问。 测试环境org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.yunzhiclub.jpa.entity.Teacher.klasses, could not initialize proxy - no Session测试环境运行时报错,no session。 ...

May 30, 2019 · 1 min · jiezi

聊聊spring data jpa的JpaQueryLookupStrategy

序本文主要研究一下spring data jpa的JpaQueryLookupStrategyQueryLookupStrategyspring-data-commons-2.1.6.RELEASE-sources.jar!/org/springframework/data/repository/query/QueryLookupStrategy.javapublic interface QueryLookupStrategy { public static enum Key { CREATE, USE_DECLARED_QUERY, CREATE_IF_NOT_FOUND; /** * Returns a strategy key from the given XML value. * * @param xml * @return a strategy key from the given XML value / @Nullable public static Key create(String xml) { if (!StringUtils.hasText(xml)) { return null; } return valueOf(xml.toUpperCase(Locale.US).replace("-", “_”)); } } /* * Resolves a {@link RepositoryQuery} from the given {@link QueryMethod} that can be executed afterwards. * * @param method will never be {@literal null}. * @param metadata will never be {@literal null}. * @param factory will never be {@literal null}. * @param namedQueries will never be {@literal null}. * @return / RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory, NamedQueries namedQueries);}QueryLookupStrategy定义了Key枚举,有CREATE, USE_DECLARED_QUERY, CREATE_IF_NOT_FOUND这些类型QueryLookupStrategy有一个抽象类为AbstractQueryLookupStrategy,它有三个实现子类,分别是CreateQueryLookupStrategy、DeclaredQueryLookupStrategy、CreateIfNotFoundQueryLookupStrategyQueryLookupStrategy还定义了resolveQuery方法用于将QueryMethod解析为RepositoryQueryRepositoryQueryspring-data-commons-2.1.6.RELEASE-sources.jar!/org/springframework/data/repository/query/RepositoryQuery.javapublic interface RepositoryQuery { /* * Executes the {@link RepositoryQuery} with the given parameters. * * @param parameters must not be {@literal null}. * @return execution result. Can be {@literal null}. / @Nullable Object execute(Object[] parameters); /* * Returns the related {@link QueryMethod}. * * @return never {@literal null}. / QueryMethod getQueryMethod();}RepositoryQuery接口定义了两个方法分别是execute、getQueryMethod;它有2个抽象类,一个是AbstractJpaQuery、另外一个是继承AbstractJpaQuery的AbstractStringBasedJpaQuery;AbstractJpaQuery有3个实现子类,分别是NamedQuery、PartTreeJpaQuery、StoredProcedureJpaQuery;AbstractStringBasedJpaQuery有两个实现子类,分别是SimpleJpaQuery及NativeJpaQueryAbstractQueryLookupStrategyspring-data-jpa-2.1.6.RELEASE-sources.jar!/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java /* * Base class for {@link QueryLookupStrategy} implementations that need access to an {@link EntityManager}. * * @author Oliver Gierke * @author Thomas Darimont / private abstract static class AbstractQueryLookupStrategy implements QueryLookupStrategy { private final EntityManager em; private final QueryExtractor provider; /* * Creates a new {@link AbstractQueryLookupStrategy}. * * @param em * @param extractor / public AbstractQueryLookupStrategy(EntityManager em, QueryExtractor extractor) { this.em = em; this.provider = extractor; } / * (non-Javadoc) * @see org.springframework.data.repository.query.QueryLookupStrategy#resolveQuery(java.lang.reflect.Method, org.springframework.data.repository.core.RepositoryMetadata, org.springframework.data.projection.ProjectionFactory, org.springframework.data.repository.core.NamedQueries) / @Override public final RepositoryQuery resolveQuery(Method method, RepositoryMetadata metadata, ProjectionFactory factory, NamedQueries namedQueries) { return resolveQuery(new JpaQueryMethod(method, metadata, factory, provider), em, namedQueries); } protected abstract RepositoryQuery resolveQuery(JpaQueryMethod method, EntityManager em, NamedQueries namedQueries); }AbstractQueryLookupStrategy实现了QueryLookupStrategy接口定义的resolveQuery方法,不过它调用了自己定义的抽象方法resolveQueryCreateQueryLookupStrategyspring-data-jpa-2.1.6.RELEASE-sources.jar!/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java /* * {@link QueryLookupStrategy} to create a query from the method name. * * @author Oliver Gierke * @author Thomas Darimont / private static class CreateQueryLookupStrategy extends AbstractQueryLookupStrategy { private final PersistenceProvider persistenceProvider; private final EscapeCharacter escape; public CreateQueryLookupStrategy(EntityManager em, QueryExtractor extractor, EscapeCharacter escape) { super(em, extractor); this.persistenceProvider = PersistenceProvider.fromEntityManager(em); this.escape = escape; } @Override protected RepositoryQuery resolveQuery(JpaQueryMethod method, EntityManager em, NamedQueries namedQueries) { return new PartTreeJpaQuery(method, em, persistenceProvider, escape); } }CreateQueryLookupStrategy继承了AbstractQueryLookupStrategy,其resolveQuery方法创建的是PartTreeJpaQueryDeclaredQueryLookupStrategyspring-data-jpa-2.1.6.RELEASE-sources.jar!/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java /* * {@link QueryLookupStrategy} that tries to detect a declared query declared via {@link Query} annotation followed by * a JPA named query lookup. * * @author Oliver Gierke * @author Thomas Darimont / private static class DeclaredQueryLookupStrategy extends AbstractQueryLookupStrategy { private final QueryMethodEvaluationContextProvider evaluationContextProvider; /* * Creates a new {@link DeclaredQueryLookupStrategy}. * * @param em * @param extractor * @param evaluationContextProvider / public DeclaredQueryLookupStrategy(EntityManager em, QueryExtractor extractor, QueryMethodEvaluationContextProvider evaluationContextProvider) { super(em, extractor); this.evaluationContextProvider = evaluationContextProvider; } / * (non-Javadoc) * @see org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy.AbstractQueryLookupStrategy#resolveQuery(org.springframework.data.jpa.repository.query.JpaQueryMethod, javax.persistence.EntityManager, org.springframework.data.repository.core.NamedQueries) / @Override protected RepositoryQuery resolveQuery(JpaQueryMethod method, EntityManager em, NamedQueries namedQueries) { RepositoryQuery query = JpaQueryFactory.INSTANCE.fromQueryAnnotation(method, em, evaluationContextProvider); if (null != query) { return query; } query = JpaQueryFactory.INSTANCE.fromProcedureAnnotation(method, em); if (null != query) { return query; } String name = method.getNamedQueryName(); if (namedQueries.hasQuery(name)) { return JpaQueryFactory.INSTANCE.fromMethodWithQueryString(method, em, namedQueries.getQuery(name), evaluationContextProvider); } query = NamedQuery.lookupFrom(method, em); if (null != query) { return query; } throw new IllegalStateException( String.format(“Did neither find a NamedQuery nor an annotated query for method %s!”, method)); } }DeclaredQueryLookupStrategy继承了AbstractQueryLookupStrategy,其resolveQuery方法借助JpaQueryFactory.INSTANCE.fromQueryAnnotation、JpaQueryFactory.INSTANCE.fromProcedureAnnotation、NamedQuery.lookupFrom来生成CreateIfNotFoundQueryLookupStrategyspring-data-jpa-2.1.6.RELEASE-sources.jar!/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.java /* * {@link QueryLookupStrategy} to try to detect a declared query first ( * {@link org.springframework.data.jpa.repository.Query}, JPA named query). In case none is found we fall back on * query creation. * * @author Oliver Gierke * @author Thomas Darimont / private static class CreateIfNotFoundQueryLookupStrategy extends AbstractQueryLookupStrategy { private final DeclaredQueryLookupStrategy lookupStrategy; private final CreateQueryLookupStrategy createStrategy; /* * Creates a new {@link CreateIfNotFoundQueryLookupStrategy}. * * @param em * @param extractor * @param createStrategy * @param lookupStrategy / public CreateIfNotFoundQueryLookupStrategy(EntityManager em, QueryExtractor extractor, CreateQueryLookupStrategy createStrategy, DeclaredQueryLookupStrategy lookupStrategy) { super(em, extractor); this.createStrategy = createStrategy; this.lookupStrategy = lookupStrategy; } / * (non-Javadoc) * @see org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy.AbstractQueryLookupStrategy#resolveQuery(org.springframework.data.jpa.repository.query.JpaQueryMethod, javax.persistence.EntityManager, org.springframework.data.repository.core.NamedQueries) / @Override protected RepositoryQuery resolveQuery(JpaQueryMethod method, EntityManager em, NamedQueries namedQueries) { try { return lookupStrategy.resolveQuery(method, em, namedQueries); } catch (IllegalStateException e) { return createStrategy.resolveQuery(method, em, namedQueries); } } }CreateIfNotFoundQueryLookupStrategy继承了AbstractQueryLookupStrategy,它的构造器要求传入DeclaredQueryLookupStrategy、CreateQueryLookupStrategy参数,其resolveQuery方法先是借助DeclaredQueryLookupStrategy来创建RepositoryQuery,如果抛出的是IllegalStateException,再借助CreateQueryLookupStrategy来创建RepositoryQueryJpaQueryLookupStrategyspring-data-jpa-2.1.6.RELEASE-sources.jar!/org/springframework/data/jpa/repository/query/JpaQueryLookupStrategy.javapublic final class JpaQueryLookupStrategy { /* * Private constructor to prevent instantiation. / private JpaQueryLookupStrategy() {} //…… /* * Creates a {@link QueryLookupStrategy} for the given {@link EntityManager} and {@link Key}. * * @param em must not be {@literal null}. * @param key may be {@literal null}. * @param extractor must not be {@literal null}. * @param evaluationContextProvider must not be {@literal null}. * @param escape * @return */ public static QueryLookupStrategy create(EntityManager em, @Nullable Key key, QueryExtractor extractor, QueryMethodEvaluationContextProvider evaluationContextProvider, EscapeCharacter escape) { Assert.notNull(em, “EntityManager must not be null!”); Assert.notNull(extractor, “QueryExtractor must not be null!”); Assert.notNull(evaluationContextProvider, “EvaluationContextProvider must not be null!”); switch (key != null ? key : Key.CREATE_IF_NOT_FOUND) { case CREATE: return new CreateQueryLookupStrategy(em, extractor, escape); case USE_DECLARED_QUERY: return new DeclaredQueryLookupStrategy(em, extractor, evaluationContextProvider); case CREATE_IF_NOT_FOUND: return new CreateIfNotFoundQueryLookupStrategy(em, extractor, new CreateQueryLookupStrategy(em, extractor, escape), new DeclaredQueryLookupStrategy(em, extractor, evaluationContextProvider)); default: throw new IllegalArgumentException(String.format(“Unsupported query lookup strategy %s!”, key)); } }}JpaQueryLookupStrategy提供一个静态方法create用于根据不同的QueryLookupStrategy.Key来创建不同的QueryLookupStrategy,默认是Key.CREATE_IF_NOT_FOUND,即创建CreateIfNotFoundQueryLookupStrategy小结QueryLookupStrategy定义了Key枚举,有CREATE, USE_DECLARED_QUERY, CREATE_IF_NOT_FOUND这些类型;QueryLookupStrategy有一个抽象类为AbstractQueryLookupStrategy,它有三个实现子类,分别是CreateQueryLookupStrategy、DeclaredQueryLookupStrategy、CreateIfNotFoundQueryLookupStrategy;QueryLookupStrategy还定义了resolveQuery方法用于将QueryMethod解析为RepositoryQueryAbstractQueryLookupStrategy实现了QueryLookupStrategy接口定义的resolveQuery方法,不过它调用了自己定义的抽象方法resolveQueryCreateQueryLookupStrategy继承了AbstractQueryLookupStrategy,其resolveQuery方法创建的是PartTreeJpaQuery(解析方法名);DeclaredQueryLookupStrategy继承了AbstractQueryLookupStrategy,其resolveQuery方法借助JpaQueryFactory.INSTANCE.fromQueryAnnotation(解析@Query注解)、JpaQueryFactory.INSTANCE.fromProcedureAnnotation(解析@Procedure注解)、NamedQuery.lookupFrom(解析@NamedQuery注解)来生成;CreateIfNotFoundQueryLookupStrategy继承了AbstractQueryLookupStrategy,它的构造器要求传入DeclaredQueryLookupStrategy、CreateQueryLookupStrategy参数,其resolveQuery方法先是借助DeclaredQueryLookupStrategy来创建RepositoryQuery,如果抛出的是IllegalStateException,再借助CreateQueryLookupStrategy来创建RepositoryQueryJpaQueryLookupStrategy提供一个静态方法create用于根据不同的QueryLookupStrategy.Key来创建不同的QueryLookupStrategy,默认是Key.CREATE_IF_NOT_FOUND,即创建CreateIfNotFoundQueryLookupStrategydocJpaQueryLookupStrategy ...

April 15, 2019 · 4 min · jiezi

聊聊spring data jpa的SimpleJpaRepository

序本文主要研究一下spring data jpa的SimpleJpaRepositoryJpaRepositoryImplementationspring-data-jpa-2.1.6.RELEASE-sources.jar!/org/springframework/data/jpa/repository/support/JpaRepositoryImplementation.java/** * SPI interface to be implemented by {@link JpaRepository} implementations. * * @author Oliver Gierke * @author Stefan Fussenegger /@NoRepositoryBeanpublic interface JpaRepositoryImplementation<T, ID> extends JpaRepository<T, ID>, JpaSpecificationExecutor<T> { /* * Configures the {@link CrudMethodMetadata} to be used with the repository. * * @param crudMethodMetadata must not be {@literal null}. / void setRepositoryMethodMetadata(CrudMethodMetadata crudMethodMetadata);}JpaRepositoryImplementation接口继承了JpaRepository及JpaSpecificationExecutor,它是JpaRepository接口实现类的SPI interface;它定义了setRepositoryMethodMetadata方法SimpleJpaRepositoryspring-data-jpa-2.1.6.RELEASE-sources.jar!/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java@Repository@Transactional(readOnly = true)public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> { private static final String ID_MUST_NOT_BE_NULL = “The given id must not be null!”; private final JpaEntityInformation<T, ?> entityInformation; private final EntityManager em; private final PersistenceProvider provider; private @Nullable CrudMethodMetadata metadata; /* * Creates a new {@link SimpleJpaRepository} to manage objects of the given {@link JpaEntityInformation}. * * @param entityInformation must not be {@literal null}. * @param entityManager must not be {@literal null}. / public SimpleJpaRepository(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) { Assert.notNull(entityInformation, “JpaEntityInformation must not be null!”); Assert.notNull(entityManager, “EntityManager must not be null!”); this.entityInformation = entityInformation; this.em = entityManager; this.provider = PersistenceProvider.fromEntityManager(entityManager); } /* * Creates a new {@link SimpleJpaRepository} to manage objects of the given domain type. * * @param domainClass must not be {@literal null}. * @param em must not be {@literal null}. / public SimpleJpaRepository(Class<T> domainClass, EntityManager em) { this(JpaEntityInformationSupport.getEntityInformation(domainClass, em), em); } /* * Configures a custom {@link CrudMethodMetadata} to be used to detect {@link LockModeType}s and query hints to be * applied to queries. * * @param crudMethodMetadata / public void setRepositoryMethodMetadata(CrudMethodMetadata crudMethodMetadata) { this.metadata = crudMethodMetadata; } @Nullable protected CrudMethodMetadata getRepositoryMethodMetadata() { return metadata; } //…… / * (non-Javadoc) * @see org.springframework.data.repository.CrudRepository#delete(java.io.Serializable) / @Transactional public void deleteById(ID id) { Assert.notNull(id, ID_MUST_NOT_BE_NULL); delete(findById(id).orElseThrow(() -> new EmptyResultDataAccessException( String.format(“No %s entity with id %s exists!”, entityInformation.getJavaType(), id), 1))); } / * (non-Javadoc) * @see org.springframework.data.repository.CrudRepository#delete(java.lang.Object) / @Transactional public void delete(T entity) { Assert.notNull(entity, “The entity must not be null!”); em.remove(em.contains(entity) ? entity : em.merge(entity)); } / * (non-Javadoc) * @see org.springframework.data.repository.CrudRepository#delete(java.lang.Iterable) / @Transactional public void deleteAll(Iterable<? extends T> entities) { Assert.notNull(entities, “The given Iterable of entities not be null!”); for (T entity : entities) { delete(entity); } } / * (non-Javadoc) * @see org.springframework.data.jpa.repository.JpaRepository#deleteInBatch(java.lang.Iterable) / @Transactional public void deleteInBatch(Iterable<T> entities) { Assert.notNull(entities, “The given Iterable of entities not be null!”); if (!entities.iterator().hasNext()) { return; } applyAndBind(getQueryString(DELETE_ALL_QUERY_STRING, entityInformation.getEntityName()), entities, em) .executeUpdate(); } / * (non-Javadoc) * @see org.springframework.data.repository.Repository#deleteAll() / @Transactional public void deleteAll() { for (T element : findAll()) { delete(element); } } / * (non-Javadoc) * @see org.springframework.data.jpa.repository.JpaRepository#deleteAllInBatch() / @Transactional public void deleteAllInBatch() { em.createQuery(getDeleteAllQueryString()).executeUpdate(); } //…… / * (non-Javadoc) * @see org.springframework.data.repository.CrudRepository#save(java.lang.Object) / @Transactional public <S extends T> S save(S entity) { if (entityInformation.isNew(entity)) { em.persist(entity); return entity; } else { return em.merge(entity); } } / * (non-Javadoc) * @see org.springframework.data.jpa.repository.JpaRepository#saveAndFlush(java.lang.Object) / @Transactional public <S extends T> S saveAndFlush(S entity) { S result = save(entity); flush(); return result; } / * (non-Javadoc) * @see org.springframework.data.jpa.repository.JpaRepository#save(java.lang.Iterable) / @Transactional public <S extends T> List<S> saveAll(Iterable<S> entities) { Assert.notNull(entities, “The given Iterable of entities not be null!”); List<S> result = new ArrayList<S>(); for (S entity : entities) { result.add(save(entity)); } return result; } / * (non-Javadoc) * @see org.springframework.data.jpa.repository.JpaRepository#flush() / @Transactional public void flush() { em.flush(); } //…… / * (non-Javadoc) * @see org.springframework.data.repository.CrudRepository#findById(java.io.Serializable) / public Optional<T> findById(ID id) { Assert.notNull(id, ID_MUST_NOT_BE_NULL); Class<T> domainType = getDomainClass(); if (metadata == null) { return Optional.ofNullable(em.find(domainType, id)); } LockModeType type = metadata.getLockModeType(); Map<String, Object> hints = getQueryHints().withFetchGraphs(em).asMap(); return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints)); } / * (non-Javadoc) * @see org.springframework.data.jpa.repository.JpaRepository#getOne(java.io.Serializable) / @Override public T getOne(ID id) { Assert.notNull(id, ID_MUST_NOT_BE_NULL); return em.getReference(getDomainClass(), id); } /* * Applies the given {@link Specification} to the given {@link CriteriaQuery}. * * @param spec can be {@literal null}. * @param domainClass must not be {@literal null}. * @param query must not be {@literal null}. * @return / private <S, U extends T> Root<U> applySpecificationToCriteria(@Nullable Specification<U> spec, Class<U> domainClass, CriteriaQuery<S> query) { Assert.notNull(domainClass, “Domain class must not be null!”); Assert.notNull(query, “CriteriaQuery must not be null!”); Root<U> root = query.from(domainClass); if (spec == null) { return root; } CriteriaBuilder builder = em.getCriteriaBuilder(); Predicate predicate = spec.toPredicate(root, query, builder); if (predicate != null) { query.where(predicate); } return root; } //……}SimpleJpaRepository实现了JpaRepositoryImplementation接口,它是CrudRepository的默认实现;它的构造器都要求传入EntityManager它的类上注解了@Transactional(readOnly = true);而对deleteById、delete、deleteAll、deleteInBatch、deleteAllInBatch、save、saveAndFlush、saveAll、flush都添加了@Transactional注解从各个方法的实现可以看到SimpleJpaRepository是使用EntityManager来完成具体的方法功能,对于查询功能很多都借助了applySpecificationToCriteria方法,将spring data的Specification转换为javax.persistence的CriteriaQueryJpaRepositoryFactoryspring-data-jpa-2.1.6.RELEASE-sources.jar!/org/springframework/data/jpa/repository/support/JpaRepositoryFactory.javapublic class JpaRepositoryFactory extends RepositoryFactorySupport { private final EntityManager entityManager; private final QueryExtractor extractor; private final CrudMethodMetadataPostProcessor crudMethodMetadataPostProcessor; private EntityPathResolver entityPathResolver; private EscapeCharacter escapeCharacter = EscapeCharacter.of(’\’); /* * Creates a new {@link JpaRepositoryFactory}. * * @param entityManager must not be {@literal null} / public JpaRepositoryFactory(EntityManager entityManager) { Assert.notNull(entityManager, “EntityManager must not be null!”); this.entityManager = entityManager; this.extractor = PersistenceProvider.fromEntityManager(entityManager); this.crudMethodMetadataPostProcessor = new CrudMethodMetadataPostProcessor(); this.entityPathResolver = SimpleEntityPathResolver.INSTANCE; addRepositoryProxyPostProcessor(crudMethodMetadataPostProcessor); if (extractor.equals(PersistenceProvider.ECLIPSELINK)) { addQueryCreationListener(new EclipseLinkProjectionQueryCreationListener(entityManager)); } } //…… / * (non-Javadoc) * @see org.springframework.data.repository.core.support.RepositoryFactorySupport#getTargetRepository(org.springframework.data.repository.core.RepositoryMetadata) / @Override protected final JpaRepositoryImplementation<?, ?> getTargetRepository(RepositoryInformation information) { JpaRepositoryImplementation<?, ?> repository = getTargetRepository(information, entityManager); repository.setRepositoryMethodMetadata(crudMethodMetadataPostProcessor.getCrudMethodMetadata()); return repository; } /* * Callback to create a {@link JpaRepository} instance with the given {@link EntityManager} * * @param information will never be {@literal null}. * @param entityManager will never be {@literal null}. * @return / protected JpaRepositoryImplementation<?, ?> getTargetRepository(RepositoryInformation information, EntityManager entityManager) { JpaEntityInformation<?, Serializable> entityInformation = getEntityInformation(information.getDomainType()); Object repository = getTargetRepositoryViaReflection(information, entityInformation, entityManager); Assert.isInstanceOf(JpaRepositoryImplementation.class, repository); return (JpaRepositoryImplementation<?, ?>) repository; } //……}JpaRepositoryFactory的getTargetRepository会根据RepositoryInformation创建JpaRepositoryImplementation,这里默认创建的是SimpleJpaRepository实例RepositoryFactorySupportspring-data-commons-2.1.6.RELEASE-sources.jar!/org/springframework/data/repository/core/support/RepositoryFactorySupport.javapublic abstract class RepositoryFactorySupport implements BeanClassLoaderAware, BeanFactoryAware { //…… /* * Returns a repository instance for the given interface backed by an instance providing implementation logic for * custom logic. * * @param repositoryInterface must not be {@literal null}. * @param fragments must not be {@literal null}. * @return * @since 2.0 */ @SuppressWarnings({ “unchecked” }) public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fragments) { if (LOG.isDebugEnabled()) { LOG.debug(“Initializing repository instance for {}…”, repositoryInterface.getName()); } Assert.notNull(repositoryInterface, “Repository interface must not be null!”); Assert.notNull(fragments, “RepositoryFragments must not be null!”); RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface); RepositoryComposition composition = getRepositoryComposition(metadata, fragments); RepositoryInformation information = getRepositoryInformation(metadata, composition); validate(information, composition); Object target = getTargetRepository(information); // Create proxy ProxyFactory result = new ProxyFactory(); result.setTarget(target); result.setInterfaces(repositoryInterface, Repository.class, TransactionalProxy.class); if (MethodInvocationValidator.supports(repositoryInterface)) { result.addAdvice(new MethodInvocationValidator()); } result.addAdvice(SurroundingTransactionDetectorMethodInterceptor.INSTANCE); result.addAdvisor(ExposeInvocationInterceptor.ADVISOR); postProcessors.forEach(processor -> processor.postProcess(result, information)); result.addAdvice(new DefaultMethodInvokingMethodInterceptor()); ProjectionFactory projectionFactory = getProjectionFactory(classLoader, beanFactory); result.addAdvice(new QueryExecutorMethodInterceptor(information, projectionFactory)); composition = composition.append(RepositoryFragment.implemented(target)); result.addAdvice(new ImplementationMethodExecutionInterceptor(composition)); T repository = (T) result.getProxy(classLoader); if (LOG.isDebugEnabled()) { LOG.debug(“Finished creation of repository instance for {}.”, repositoryInterface.getName()); } return repository; } //……}RepositoryFactorySupport的getRepository方法在调用子类的getTargetRepository创建SimpleJpaRepository实例之后,会对其进行proxy,设置其接口为用户定义的dao接口、Repository、TransactionalProxy,并添加了SurroundingTransactionDetectorMethodInterceptor、DefaultMethodInvokingMethodInterceptor、QueryExecutorMethodInterceptor、ImplementationMethodExecutionInterceptor等MethodInterceptor,最后生成最终的实现类小结JpaRepositoryImplementation接口继承了JpaRepository及JpaSpecificationExecutor,它是JpaRepository接口实现类的SPI interface;它定义了setRepositoryMethodMetadata方法SimpleJpaRepository实现了JpaRepositoryImplementation接口,它是CrudRepository的默认实现;它的构造器都要求传入EntityManager;从各个方法的实现可以看到SimpleJpaRepository是使用EntityManager来完成具体的方法功能,对于查询功能很多都借助了applySpecificationToCriteria方法,将spring data的Specification转换为javax.persistence的CriteriaQueryJpaRepositoryFactory的getTargetRepository会根据RepositoryInformation创建JpaRepositoryImplementation,这里默认创建的是SimpleJpaRepository实例;RepositoryFactorySupport的getRepository方法在调用子类的getTargetRepository创建SimpleJpaRepository实例之后,会对其进行proxy,设置其接口为用户定义的dao接口、Repository、TransactionalProxy,并添加了SurroundingTransactionDetectorMethodInterceptor、DefaultMethodInvokingMethodInterceptor、QueryExecutorMethodInterceptor、ImplementationMethodExecutionInterceptor等MethodInterceptor,最后生成最终的实现类docSimpleJpaRepositorySimpleJpaRepository.javaCustomizing Spring Data JPA RepositorySpring Data JPA – Adding a Method in All RepositoriesSpring Data JPA Tutorial: Adding Custom Methods to All Repositories ...

April 14, 2019 · 5 min · jiezi

聊聊spring data jpa的OpenSessionInView

序本文主要研究一下spring data jpa的OpenSessionInViewOpen Session In ViewOpen Session In View简称OSIV,是为了解决在mvc的controller中使用了hibernate的lazy load的属性时没有session抛出的LazyInitializationException异常;对hibernate来说ToMany关系默认是延迟加载,而ToOne关系则默认是立即加载JpaPropertiesspring-boot-autoconfigure-2.1.4.RELEASE-sources.jar!/org/springframework/boot/autoconfigure/orm/jpa/JpaProperties.java@ConfigurationProperties(prefix = “spring.jpa”)public class JpaProperties { /** * Additional native properties to set on the JPA provider. / private Map<String, String> properties = new HashMap<>(); /* * Mapping resources (equivalent to “mapping-file” entries in persistence.xml). / private final List<String> mappingResources = new ArrayList<>(); /* * Name of the target database to operate on, auto-detected by default. Can be * alternatively set using the “Database” enum. / private String databasePlatform; /* * Target database to operate on, auto-detected by default. Can be alternatively set * using the “databasePlatform” property. / private Database database; /* * Whether to initialize the schema on startup. / private boolean generateDdl = false; /* * Whether to enable logging of SQL statements. / private boolean showSql = false; /* * Register OpenEntityManagerInViewInterceptor. Binds a JPA EntityManager to the * thread for the entire processing of the request. / private Boolean openInView; //……}JpaProperties有一个配置项为openInView(默认为true),用于决定是否注册OpenEntityManagerInViewInterceptor,它会一个请求线程绑定一个JPA EntityManagerJpaBaseConfigurationspring-boot-autoconfigure-2.1.4.RELEASE-sources.jar!/org/springframework/boot/autoconfigure/orm/jpa/JpaBaseConfiguration.java@Configuration@EnableConfigurationProperties(JpaProperties.class)@Import(DataSourceInitializedPublisher.Registrar.class)public abstract class JpaBaseConfiguration implements BeanFactoryAware { //…… @Configuration @ConditionalOnWebApplication(type = Type.SERVLET) @ConditionalOnClass(WebMvcConfigurer.class) @ConditionalOnMissingBean({ OpenEntityManagerInViewInterceptor.class, OpenEntityManagerInViewFilter.class }) @ConditionalOnMissingFilterBean(OpenEntityManagerInViewFilter.class) @ConditionalOnProperty(prefix = “spring.jpa”, name = “open-in-view”, havingValue = “true”, matchIfMissing = true) protected static class JpaWebConfiguration { // Defined as a nested config to ensure WebMvcConfigurerAdapter is not read when // not on the classpath @Configuration protected static class JpaWebMvcConfiguration implements WebMvcConfigurer { private static final Log logger = LogFactory .getLog(JpaWebMvcConfiguration.class); private final JpaProperties jpaProperties; protected JpaWebMvcConfiguration(JpaProperties jpaProperties) { this.jpaProperties = jpaProperties; } @Bean public OpenEntityManagerInViewInterceptor openEntityManagerInViewInterceptor() { if (this.jpaProperties.getOpenInView() == null) { logger.warn(“spring.jpa.open-in-view is enabled by default. " + “Therefore, database queries may be performed during view " + “rendering. Explicitly configure " + “spring.jpa.open-in-view to disable this warning”); } return new OpenEntityManagerInViewInterceptor(); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addWebRequestInterceptor(openEntityManagerInViewInterceptor()); } } } //……}JpaBaseConfiguration里头有个JpaWebMvcConfiguration配置,在web application的类型是Type.SERVLET的时候,且spring.jpa.open-in-view不是false的时候注册OpenEntityManagerInViewInterceptor,然后添加到mvc的webRequestInterceptor中OpenEntityManagerInViewInterceptorspring-orm-5.1.6.RELEASE-sources.jar!/org/springframework/orm/jpa/support/OpenEntityManagerInViewInterceptor.javapublic class OpenEntityManagerInViewInterceptor extends EntityManagerFactoryAccessor implements AsyncWebRequestInterceptor { /* * Suffix that gets appended to the EntityManagerFactory toString * representation for the “participate in existing entity manager * handling” request attribute. * @see #getParticipateAttributeName / public static final String PARTICIPATE_SUFFIX = “.PARTICIPATE”; @Override public void preHandle(WebRequest request) throws DataAccessException { String key = getParticipateAttributeName(); WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); if (asyncManager.hasConcurrentResult() && applyEntityManagerBindingInterceptor(asyncManager, key)) { return; } EntityManagerFactory emf = obtainEntityManagerFactory(); if (TransactionSynchronizationManager.hasResource(emf)) { // Do not modify the EntityManager: just mark the request accordingly. Integer count = (Integer) request.getAttribute(key, WebRequest.SCOPE_REQUEST); int newCount = (count != null ? count + 1 : 1); request.setAttribute(getParticipateAttributeName(), newCount, WebRequest.SCOPE_REQUEST); } else { logger.debug(“Opening JPA EntityManager in OpenEntityManagerInViewInterceptor”); try { EntityManager em = createEntityManager(); EntityManagerHolder emHolder = new EntityManagerHolder(em); TransactionSynchronizationManager.bindResource(emf, emHolder); AsyncRequestInterceptor interceptor = new AsyncRequestInterceptor(emf, emHolder); asyncManager.registerCallableInterceptor(key, interceptor); asyncManager.registerDeferredResultInterceptor(key, interceptor); } catch (PersistenceException ex) { throw new DataAccessResourceFailureException(“Could not create JPA EntityManager”, ex); } } } @Override public void postHandle(WebRequest request, @Nullable ModelMap model) { } @Override public void afterCompletion(WebRequest request, @Nullable Exception ex) throws DataAccessException { if (!decrementParticipateCount(request)) { EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager.unbindResource(obtainEntityManagerFactory()); logger.debug(“Closing JPA EntityManager in OpenEntityManagerInViewInterceptor”); EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager()); } } private boolean decrementParticipateCount(WebRequest request) { String participateAttributeName = getParticipateAttributeName(); Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST); if (count == null) { return false; } // Do not modify the Session: just clear the marker. if (count > 1) { request.setAttribute(participateAttributeName, count - 1, WebRequest.SCOPE_REQUEST); } else { request.removeAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST); } return true; } @Override public void afterConcurrentHandlingStarted(WebRequest request) { if (!decrementParticipateCount(request)) { TransactionSynchronizationManager.unbindResource(obtainEntityManagerFactory()); } } /* * Return the name of the request attribute that identifies that a request is * already filtered. Default implementation takes the toString representation * of the EntityManagerFactory instance and appends “.FILTERED”. * @see #PARTICIPATE_SUFFIX */ protected String getParticipateAttributeName() { return obtainEntityManagerFactory().toString() + PARTICIPATE_SUFFIX; } private boolean applyEntityManagerBindingInterceptor(WebAsyncManager asyncManager, String key) { CallableProcessingInterceptor cpi = asyncManager.getCallableInterceptor(key); if (cpi == null) { return false; } ((AsyncRequestInterceptor) cpi).bindEntityManager(); return true; }}OpenEntityManagerInViewInterceptor继承了抽象类EntityManagerFactoryAccessor,实现了AsyncWebRequestInterceptor接口(定义了afterConcurrentHandlingStarted方法);AsyncWebRequestInterceptor继承了WebRequestInterceptor(定义了preHandle、postHandle、afterCompletion方法)preHandle方法会判断当前线程是否有EntityManagerFactory,如果有的话则会在request的attribute中维护count;如果没有的话则会创建EntityManager(openSession),然后使用TransactionSynchronizationManager.bindResource进行绑定afterCompletion方法会先对request attribute中的count进行递减(如果有的话),当count为0的时候移除该attribute;如果request没有count则使用TransactionSynchronizationManager.unbindResource进行解绑,然后关闭EntityManager;异步的afterConcurrentHandlingStarted方法也类似,主要是进行unbind操作小结对hibernate来说ToMany关系默认是延迟加载,而ToOne关系则默认是立即加载;而在mvc的controller中脱离了persisent contenxt,于是entity变成了detached状态,这个时候要使用延迟加载的属性时就会抛出LazyInitializationException异常,而Open Session In View指在解决这个问题JpaBaseConfiguration里头有个JpaWebMvcConfiguration配置,在web application的类型是Type.SERVLET的时候,且spring.jpa.open-in-view不是false的时候注册OpenEntityManagerInViewInterceptor,然后添加到mvc的webRequestInterceptor中OpenEntityManagerInViewInterceptor的preHandle方法会判断当前线程是否有EntityManagerFactory,如果没有则会创建EntityManager(openSession),然后使用TransactionSynchronizationManager.bindResource绑定到当前线程;afterCompletion方法会使用TransactionSynchronizationManager.unbindResource进行解绑,然后关闭EntityManager通过OSIV技术来解决LazyInitialization问题会导致open的session生命周期过长,它贯穿整个request,在view渲染完之后才能关闭session释放数据库连接;另外OSIV将service层的技术细节暴露到了controller层,造成了一定的耦合,因而不建议开启,对应的解决方案就是在controller层中使用dto,而非detached状态的entity,所需的数据不再依赖延时加载,在组装dto的时候根据需要显式查询docEager/Lazy Loading In HibernateOpen Session in ViewOpen Session In View模式的基本常识The Open Session In View Anti-PatternOpen Session In View Design TradeoffsWhy is Hibernate Open Session in View considered a bad practice?Log a warning on startup when spring.jpa.open-in-view is enabled but user has not explicitly opted in #7107SPRING BOOT BEST PRACTICE – DISABLE OSIV TO START RECEIVING LAZYINITIALIZATIONEXCEPTION WARNINGS AGAIN ...

April 14, 2019 · 4 min · jiezi

Hibernate5.2-5.3版本的ManyToOne、OneToOne的延迟加载(fetch=Lazy)失效

项目使用springboot的1.5.19版本进行开发,里面使用到JPA,而springboot这个版本自带的JPA实现是Hibernate的5.0.12版本。这个版本里面的延迟加载是没有问题的,当你设置 fetch = FetchType.LAZY 时,关联的对象在你没有使用的时候,是不会发出sql的。但升级了springboot的2.1.3后,依赖的Hibernate已经去到5.3.7版本了。这时候延迟加载就失效了,就算只是查询一个字段,也会再发多一条关联对象的SQL出来。这个问题,在升级Hibernate5.4.2版本后是解决了的。而对于使用springboot 2.1.3的话,需要先排除掉spring自身加载的Hibernate版本,自己替换成Hibernate5.4.2版本才可以。不知道有没有人遇到这问题,本人测试,Hibernate5.2.x时候就已经出现了,但好像一直没有修复。

March 27, 2019 · 1 min · jiezi

几个数据持久化框架Hibernate、JPA、Mybatis、JOOQ和JDBC Template的比较

因为项目需要选择数据持久化框架,看了一下主要几个流行的和不流行的框架,对于复杂业务系统,最终的结论是,JOOQ是总体上最好的,可惜不是完全免费,最终选择JDBC Template。Hibernate和Mybatis是使用最多的两个主流框架,而JOOQ、Ebean等小众框架则知道的人不多,但也有很多独特的优点;而JPA则是一组Java持久层Api的规范,Spring Data JPA是JPA Repository的实现,本来和Hibernate、Mybatis、JOOQ之类的框架不在同一个层次上,但引入Spring Data JPA之类框架之后,我们会直接使用JPA的API查询更新数据库,就像我们使用Mybatis一样,所以这里也把JPA和其他框架放在一起进行比较。同样,JDBC和其他框架也在同一层次,位于所有持久框架的底层,但我们有时候也会直接在项目中使用JDBC,而Spring JDBC Template部分消除了使用JDBC的繁琐细节,降低了使用成本,使得我们更加愿意在项目中直接使用JDBC。一、SQL封装和性能在使用Hibernate的时候,我们查询的是POJO实体类,而不再是数据库的表,例如hql语句 select count(*) from User,里面的User是一个Java类,而不是数据库表User。这符合ORM最初的理想,ORM认为Java程序员使用OO的思维方式,和关系数据库的思维方式差距巨大,为了填补对象和关系思维方式的鸿沟,必须做一个对象到关系的映射,然后在Java的对象世界中,程序员可以使用纯的对象的思维方式,查询POJO对象,查询条件是对象属性,不再需要有任何表、字段等关系的概念,这样java程序员就更容易做持久层的操作。JPA可以视为Hibernate的儿子,也继承了这个思路,把SQL彻底封装起来,让Java程序员看不到关系的概念,用纯的面向对象思想,重新创造一个新的查询语言代替sql,比如hql,还有JPQL等。支持JPA的框架,例如Ebean都属于这种类型的框架。但封装SQL,使用另一种纯的面向对象查询语言代替sql,真的能够让程序员更容易实现持久层操作吗?MyBatis的流行证明了事实并非如此,至少在大多数情况下,使用hql并不比使用sql简单。首先,从很多角度上看,hql/JPQL等语言更加复杂和难以理解;其次就是性能上明显降低,速度更慢,内存占用巨大,而且还不好优化。最为恼火的是,当关系的概念被替换为对象的概念之后,查询语言的灵活性变得很差,表达能力也比sql弱很多。写查询语句的时候受到各种各样的限制,一个典型的例子就是多表关联查询。不管是hibernate还是jpa,表之间的连接查询,被映射为实体类之间的关联关系,这样,如果两个实体类之间没有(实现)关联关系,你就不能把两个实体(或者表)join起来查询。这是很恼火的事情,因为我们很多时候并不需要显式定义两个实体类之间的关联关系就可以实现业务逻辑,如果使用hql,只是为了join我们就必须在两个实体类之间添加代码,而且还不能逆向工程,如果表里面没有定义外键约束的话,逆向工程会把我们添加的关联代码抹掉。MyBatis则是另外一种类型的持久化框架,它没有封装SQL也没有创建一种新的面相对象的查询语言,而是直接使用SQL作为查询语言,只是把结果填入POJO对象而已。使用sql并不比hql和JPQL困难,查询速度快,可以灵活使用任意复杂的查询只要数据库支持。从SQL封装角度上看,MyBatis比Hibernate和JPA成功,SQL本不该被封装和隐藏,让Java程序员使用SQL既不麻烦也更容易学习和上手,这应该是MyBatis流行起来的重要原因。轻量级持久层框架JOOQ也和MyBatis一样,直接使用SQL作为查询语言,比起MyBatis,JOOQ虽然知名度要低得多,但JOOQ不但和MyBatis一样可以利用SQL的灵活性和高效率,通过逆向工程,JOOQ还可以用Java代码来编写SQL语句,利用IDE的代码自动补全功能,自动提示表名和字段名,减少程序员记忆负担,还可以在元数据发生变化时发生编译错误,提示程序员修改相应的SQL语句。Ebean作为一种基于JPA的框架,它也使用JPQL语言进行查询,多数情况下会让人很恼火。但据说Ebean不排斥SQL,可以直接用SQL查询,也可以用类似JOOQ的DSL方式在代码中构造SQL语句(还是JPQL语句?),但没用过Ebean,所以具体细节不清楚。JDBC Template就不用说了,它根本没做ORM,当然是纯SQL查询。利用Spring框架,可以把JDBC Template和JPA结合起来使用,在JPA不好查询的地方,或者效率低不好优化的地方使用JDBC,缓解了Hibernate/JPA封装SQL造成的麻烦,但我仍没看到任何封装SQL的必要性,除了给程序员带来一大堆麻烦和学习负担之外,没有太明显的好处。二、DSL和变化适应性为了实现复杂的业务逻辑,不论是用SQL还是hql或者JPQL,我们都不得不写很多简单的或者复杂的查询语句,ORM无法减少这部分工作,最多是用另一种面向对象风格的语言去表达查询需求,如前所述,用面向对象风格的语言不见得比SQL更容易。通常业务系统中会有很多表,每个表都有很多字段,即便是编写最简单的查询语句也不是一件容易的事情,需要记住数据库中有哪些表,有哪些字段,记住有哪些函数等。写查询语句很多时候成为一件头疼的事情。QueryDSL、JOOQ、Ebean甚至MyBatis和JPA都设计一些特性,帮助开发人员编写查询语句,有人称之为“DSL风格数据库编程”。最早实现这类功能的可能是QueryDSL,把数据库的表结构逆向工程为java的类,然后可以让java程序员能够用java的语法构造出一个复杂的查询语句,利用IDE的代码自动补全功能,可以自动提示表名、字段名、查询语句的关键字等,很成功的简化了查询语句的编写,免除了程序员记忆各种名字、函数和关键字的负担。QueryDSL有很多版本,但用得多的是QueryDSL JPA,可以帮助开发人员编写JPQL语句,如前所述,JPQL语句有很多局限不如SQL灵活高效。后来的JOOQ和Ebean,基本上继承了QueryDSL的思路,Ebean基本上还是JPA风格的ORM框架,虽然也支持SQL,但不清楚其DSL特性是否支持SQL语句编写,在官网上看到的例子都是用于构造JPQL语句。这里面最成功的应该是JOOQ,和QueryDSL不同,JOOQ的DSL编程是帮助开发人员编写SQL语句,抛弃累赘的ORM概念,JOOQ这个功能非常轻小,非常容易学习和使用,同时性能也非常好,不像QueryDSL和Ebean,需要了解复杂的JPA概念和各种奇异的限制,JOOQ编写的就是普通的SQL语句,只是把查询结果填充到实体类中(严格说JOOQ没有实体类,只是自动生成的Record对象),JOOQ甚至不一定要把结果转换为实体类,可以让开发人员按照字段取得结果的值,相对于JDBC,JOOQ会把结果值转换为合适的Java类型,用起来比JDBC更简单。传统主流的框架对DSL风格支持得很少,Hibernate里面基本上没有看到有这方面的特性。MyBatis提供了"SQL语句构建器"来帮助开发人员构造SQL语句,但和QueryDSL/JOOQ/Ebean差很多,不能提示表名和字段名,语法也显得累赘不像SQL。JPA给人的印象是复杂难懂,它的MetaModel Api继承了特点,MetaModel API+Criteria API,再配合Hibernate JPA 2 Metamodel Generator,让人有点QueryDSL JPA的感觉,只是绕了一个大大的弯,叠加了好几层技术,最后勉强实现了QueryDSL JPA的简单易懂的功能。很多人不推荐JPA+QueryDSL的用法,而是推荐JPA MetaModel API+Criteria API+Hibernate JPA 2 Metamodel Generator的用法,让人很难理解,也许是因为这个方案是纯的标准的JPA方案。数据库DSL编程的另一个主要卖点是变化适应性强,数据库表结构在开发过程中通常会频繁发生变化,传统的非DSL编程,字段名只是一个字符串,如果字段名或者类型改变之后,查询语句没有相应修改,编译不会出错,也容易被开发人员忽略,是bug的一个主要来源。DSL编程里面,字段被逆向工程为一个java类的属性,数据库结构改变之后,作为java代码一部分的查询语句会发生编译错误,提示开发人员进行修改,可以减少大量bug,减轻测试的负担,提高软件的可靠性和质量。三、跨数据库移植Hibernate和JPA使用hql和JPQL这类数据库无关的中间语言描述查询,可以在不同数据库中无缝移植,移植到一个SQL有巨大差别的数据库通常不需要修改代码或者只需要修改很少的代码。Ebean如果不使用原生SQL,而是使用JPA的方式开发,也能在不同数据库中平滑的移植。MyBatis和JOOQ直接使用SQL,跨数据库移植时都难免要修改SQL语句。这方面MyBatis比较差,只有一个动态SQL提供的特性,对于不同的数据库编写不同的sql语句。JOOQ虽然无法像Hibernate和JPA那样无缝移植,但比MyBatis好很多。JOOQ的DSL很大一部分是通用的,例如分页查询中,Mysql的limit/offset关键字是很方便的描述方式,但Oracle和SQLServer的SQL不支持,如果我们用JOOQ的DSL的limit和offset方法构造SQL语句,不修改移植到不支持limit/offset的Oracle和SQLServer上,我们会发现这些语句还能正常使用,因为JOOQ会把limit/offset转换成等价的目标数据库的SQL语句。JOOQ根据目标数据库转换SQL语句的特性,使得在不同数据库之间移植的时候,只需要修改很少的代码,明显优于MyBatis。JDBC Template应该最差,只能尽量使用标准sql语句来减少移植工作量。四、安全性一般来说,拼接查询语句都会有安全隐患,容易被sql注入攻击。不论是jdbc,还是hql/JPQL,只要使用拼接的查询语句都是不安全的。对于JDBC来说,使用参数化的sql语句代替拼接,可以解决问题。而JPA则应该使用Criteria API解决这个问题。对于JOOQ之类的DSL风格框架,最终会被render为参数化的sql,天生免疫sql注入攻击。Ebean也支持DSL方式编程,也同样免疫sql注入攻击。这是因为DSL风格编程参数化查询比拼接字符串查询更简单,没人会拼接字符串。而jdbc/hql/JPQL拼接字符串有时候比参数化查询更简单,特别是jdbc,很多人会偷懒使用不安全的方式。五、JOOQ的失败之处可能大部分人会不同意,虽然Hibernate、JPA仍然大行其道,是最主流的持久化框架,但其实这种封装SQL的纯正ORM已经过时,效益低于使用它们的代价,应该淘汰了。MyBatis虽然有很多优点,但它的优点JOOQ基本上都有,而且多数还更好。MyBatis最大的缺点是难以避免写xml文件,xml文件编写困难,容易出错,还不容易查找错误。相对于JOOQ,MyBatis在多数情况下没有任何优势。Ebean同时具有很多不同框架的优点,但它是基于JPA的,难免有JPA的各种限制,这是致命的缺点。JOOQ这个极端轻量级的框架技术上是最完美的,突然有一天几个Web系统同时崩了,最后发现是JOOQ试用期过期了,这是JOOQ的失败之处,它不是完全免费的,只是对MySql之类的开源数据库免费。最终,我决定选择JDBC Template。

March 12, 2019 · 1 min · jiezi

springboot+jpa 整合与基本应用

什么是jpaJPA (The Java Persistence API)是用于访问,持久化和管理 Java 对象/类与关系型数据库之间的数据交互的 Java 规范。JPA 被定义为EJB (Enterprise JavaBeans) 3.0规范的一部分,作为 EJB 2 CMP 实体 Bean 规范的替代。注意,JPA 只是一个标准,只定义了一系列接口,而没有具体的实现。很多企业级框架提供了对 JPA 的实现,如 Spring 。因此 Spring 本身与 JPA 无关,只是提供了对 JPA 的支持,因此在 Spring 中你也会看到很多注解都是属于 javax.persistence 包的。JPA 允许 POJO(Plain Old Java Objects)轻松地持久化,而不需要类来实现 EJB 2 CM P规范所需的任何接口或方法。 JPA 还允许通过注解或 XML 定义对象的关系映射,定义 Java 类如何映射到关系数据库表。 JPA 还定义了一个运行时 EntityManager API,用于处理对象的查询和管理事务。 同时,JPA 定义了对象级查询语言 JPQL,以允许从数据库中查询对象,实现了对数据库的解耦合,提高了程序的可移植性,而不具体依赖某一底层数据库。JPA 是 Java 持久化规范中的一个最新版本。第一个版本是 OMG 持久性服务 Java 绑定,但这个一个失败的产品,甚至没有任何商业产品支持它。接下来的版本是 EJB 1.0 CMP Entity Beans,它已经非常成功地被大型 Java EE 提供程序(BEA,IBM)采用,但是它复杂性太高而且性能比较差。EJB 2.0 CMP 试图通过引入本地接口来减少 Entity Bean 的一些复杂性,但是大多数复杂性仍然存在,而且缺乏可移植性。历史总是要向前发展的,种种的这些使得 EJB 3.0 规范将降低复杂性作为主要目标,这导致规范委员会沿着 JPA 的路径前进。 JPA 旨在统一 EJB 2 CMP,JDO,Hibernate,从目前来看,JPA 的确取得了成功。目前大多数持久化供应商已经发布了 JPA 的实现,并被行业和用户采用。这些包括 Hibernate(由 JBoss 和 Red Hat 收购),TopLink(由 Oracle 收购)和 Kodo JDO(由 BEA 和 Oracle 收购)。其他支持 JPA 的产品包括 Cocobase(由 Thought Inc. 收购)和 JPOX。Spring Boot JPA - 基本使用导入jar在pom.xml中加入依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId></dependency>创建实体@Entitypublic class User{@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Integer id;private String phone; public String getPhone() { return phone;}public void setPhone(String phone) { this.phone = phone;}}Dao层接口public interface UserDao extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User> {User findByPhone(String phone);User findByPhoneAndFlag(String phone, Integer flag);User findByIdAndFlag(Integer userId, Integer flag);User findByOpenIdAndFlag(String openId, Integer flag);Page<User> findByFlag(Integer flag, Pageable pageable);List<User> findByNewPersonAndFlagOrderByCreateTimeAsc(Integer isNewPerson, Integer flag);User findById(Integer toBeFollowID);List<User> findByFlagAndNewPerson(Integer flag, Integer isNewPerson, Pageable pageable);List<User> findByNicenameIsLikeAndFlagAndNewPerson(String searchName, Integer flag, Integer isNewPerson, Pageable pageable);}spring data jpa 默认预先生成了一些基本的CURD的方法,例如:增、删、改等等 userDao.save(user); //保存一个对象 userDao.save(new List<User>); //保存多个对象 userDao.delete(user); //删除一个对象 userDao.delete(id); //通过id删除 userDao.deleteAll(); //删除所有 userDao.delete(new ArrayList<>()); //批量删除 userDao.findOne(id); //通过id获取 userDao.getOne(id); //通过id获取 不推荐使用 userDao.findAll(pageable); //分页查找所有 userDao.exists(id); //id是否存在 ……除此之外还提供了自定义方法名的方式查询(在userDao中)User findByOpenIdAndFlag(String openId, Integer flag);//等同于SELECT * FROM ‘user’ WHERE open_id =?1 AND flag = ?2具体的关键字,使用方法和生产成SQL如下表所示Keyword Sample JPQL snippetAnd findByLastnameAndFirstname where x.lastname = ?1 and x.firstname = ?2Or findByLastnameOrFirstname where x.lastname = ?1 or x.firstname = ?2Is,Equals findByFirstname,findByFirstnameIs,findByFirstnameEquals where x.firstname = 1?Between findByStartDateBetween where x.startDate between 1? and ?2LessThan findByAgeLessThan where x.age < ?1LessThanEqual findByAgeLessThanEqual where x.age <= ?1GreaterThan findByAgeGreaterThan where x.age > ?1GreaterThanEqual findByAgeGreaterThanEqual where x.age >= ?1After findByStartDateAfter where x.startDate > ?1Before findByStartDateBefore where x.startDate < ?1IsNull findByAgeIsNull where x.age is nullIsNotNull,NotNull findByAge(Is)NotNull where x.age not nullLike findByFirstnameLike where x.firstname like ?1NotLike findByFirstnameNotLike where x.firstname not like ?1StartingWith findByFirstnameStartingWith where x.firstname like ?1EndingWith findByFirstnameEndingWith where x.firstname like ?1Containing findByFirstnameContaining where x.firstname like ?1OrderBy findByAgeOrderByLastnameDesc where x.age = ?1 order by x.lastname descNot findByLastnameNot where x.lastname <> ?1In findByAgeIn(Collection<Age> ages) where x.age in ?1NotIn findByAgeNotIn(Collection<Age> age) where x.age not in ?1True findByActiveTrue() where x.active = trueFalse findByActiveFalse() where x.active = falseIgnoreCase findByFirstnameIgnoreCase where UPPER(x.firstame) = UPPER(?1)分页查询直接在controller层封装好Pageable对象即可@GetMapping(“findBanners”)public Page<Banner> findBanners(@PageableDefault(sort = {“priority”}, direction=Sort.Direction.ASC) Pageable pageable)==注意: #ad1f1f==前端直接在请求的最后拼接上?page=0&size=10如果前端不传page 和 size 这两个参数过来,那么@PageableDefault会默认为第1页开始,每页最大条数为10。需注意page为0时为第一页。在service中调用即可//返回给客户端的Page对象,其json格式为 {“content”: [],//数据内容 “first”: true,//是否为第一页 “last”: true,//是否为最后一页 “number”: 0,//当前页码 “numberOfElements”: 0,//当前页中的实际数据条数 “size”: 0,//一页最大条数 “sort”: { },//排序信息 “totalElements”: 0,//总条数 “totalPages”: 0//总页数 }自定义分页public Page<Banner> findBanners(int id){ int page = 1; int size = 10; Sort sort = new Sort(Sort.Direction.DESC,“priority”); Pageable pageable = new PageRequest(page,size,sort); return bannserDao.findById(id,pageable);}动态查询public Page<Admin> findAdminList(AdminCmsSearchVO adminCmsSearchVO, Pageable pageable) { Specifications<Admin> spec = Specifications.where(commonSpecUtil.like(“name”, adminCmsSearchVO.getName())) .and(commonSpecUtil.equal(“clazzType”, adminCmsSearchVO.getClazzType())) .and(commonSpecUtil.equal(“flag”,ModelContants.AdminContant.FLAG_IS_TRUE)); return adminDao.findAll(spec, pageable);}//(此代码由建东提供)package com.luwei.common.utils;import org.springframework.data.jpa.domain.Specification;import org.springframework.stereotype.Component;import java.util.Date;import java.util.List;/Created by jdq on 2017/8/8.*/@Componentpublic class CommonSpecUtil<T> {/ * 精确匹配(equal) * * @param srcName 字段名 * @param targetProperty 匹配内容 * @return /public Specification<T> equal(String srcName, Object targetProperty) { if (targetProperty == null) { return null; } return (root, query, cb) -> cb.equal(root.get(srcName), targetProperty);}/* * 精确匹配(notEqual) * * @param srcName 字段名 * @param targetProperty 匹配内容 * @return /public Specification<T> notEqual(String srcName, Object targetProperty) { if (targetProperty == null) { return null; } return (root, query, cb) -> cb.notEqual(root.get(srcName), targetProperty);}/* * 模糊匹配(like) * * @param srcName 字段名 * @param targetProperty 匹配内容 * @return /public Specification<T> like(String srcName, String targetProperty) { if (StringUtils.isEmpty(targetProperty)) { return null; } return (root, query, cb) -> cb.like(root.get(srcName), “%” + targetProperty + “%”);}/* * 日期范围匹配(timeBetween) * * @param srcName 字段名 * @param startTimeStr 开始时间 * @param endTimeStr 结束时间 * @return /public Specification<T> timeBetween(String srcName, String startTimeStr, String endTimeStr) { Date startTime, endTime; if (StringUtils.isEmpty(startTimeStr)) { startTime = DateUtils.getDate2(“1970-01-01 00:00:00”); } else { startTime = DateUtils.getDate2(startTimeStr + " 00:00:00"); } if (StringUtils.isEmpty(endTimeStr)) { endTime = new Date(); } else { endTime = DateUtils.getDate2(endTimeStr + " 23:59:59"); } return (root, query, cb) -> cb.between(root.get(srcName), startTime, endTime);}public Specification<T> parkingOrderTime(String srcName,String startTimeStr,String endTimeStr) { Date startTime,endTime; startTime=DateUtils.getDate2(DateUtils.tostartDayTime(startTimeStr)); endTime = DateUtils.getDate2(DateUtils.toEndDayTime(endTimeStr)); return (root, query, cb) -> cb.between(root.get(srcName), startTime,endTime);}/* * 日期范围匹配(timeBetween) * * @param srcName 字段名 * @param startTime 开始时间 * @param endTime 结束时间 * @return /public Specification<T> timeBetween(String srcName, Date startTime, Date endTime) { if (org.springframework.util.StringUtils.isEmpty(startTime)) { return null; } if (org.springframework.util.StringUtils.isEmpty(endTime)) { return null; } return (root, query, cb) -> cb.between(root.get(srcName), startTime, endTime);}/* * 数值范围匹配(between) * * @param srcName 字段名 * @param start 开始 * @param end 结束 * @return /public Specification<T> between(String srcName, Integer start, Integer end) { if (org.springframework.util.StringUtils.isEmpty(start)) { return null; } if (org.springframework.util.StringUtils.isEmpty(end)) { return null; } return (root, query, cb) -> cb.between(root.get(srcName), start, end);}/* * 大于等于(greaterThanOrEqualTo) * * @param srcName 字段名 * @param value 数值 * @return /public Specification<T> greaterThanOrEqualTo(String srcName, Integer value) { if (org.springframework.util.StringUtils.isEmpty(value)) { return null; } return (root, query, cb) -> cb.greaterThanOrEqualTo(root.get(srcName), value);}/* * 小于等于(lessThanOrEqualTo) * * @param srcName 字段名 * @param value 数值 * @return /public Specification<T> lessThanOrEqualTo(String srcName, Integer value) { if (org.springframework.util.StringUtils.isEmpty(value)) { return null; } return (root, query, cb) -> cb.lessThanOrEqualTo(root.get(srcName), value);}/* * in条件帅选(in) * * @param srcName 字段名 * @param list 集合 * @return /public Specification<T> in(String srcName, List<Integer> list) { if (org.springframework.util.StringUtils.isEmpty(list)) { return null; } return (root, query, cb) -> cb.and(root.get(srcName).in(list));}/* * 不为空(isNotNull) * * @param srcName 字段名 * @return /public Specification<T> isNotNull(String srcName) { return (root, query, cb) -> cb.isNotNull(root.get(srcName));}/* * 倒序(desc) * * @param srcName 字段名 * @return /public Specification<T> desc(String srcName) { return (root, query, cb) -> query.orderBy(cb.desc(root.get(srcName).as(Integer.class))).getRestriction();}/* * 升序(asc) * * @param srcName 字段名 * @return */public Specification<T> asc(String srcName) { return (root, query, cb) -> query.orderBy(cb.asc(root.get(srcName).as(Integer.class))).getRestriction();}}动态查找的条件:1.adminDao要继承JpaSpecificationExecutorenter description here2.CommonSpecUtil提供了各种匹配的方法。如equals,like,notEqual……参考博客部分引用于袁荻的博客原文地址 ...

December 14, 2018 · 5 min · jiezi