关于spring-data-jpa:spring-data-jpa-关于父实体保存而子实体存在id会进行多余查询的问题

先对题目给个具体的解释,其实是个级联保留的问题,我上一段实体代码 @Table(name="open_app_user")@Entitypublic class OpenAppUser extends BaseEntity implements UserDetails { /** * 所属利用id */ @ApiModelProperty(value="所属利用id") @ManyToOne @JoinColumn(name = "app_id") //private Integer appId; private OpenApp openApp; // ...}@Table(name="open_app")@Entitypublic class OpenApp extends BaseEntity { // ...}这里有两个实体,一个是父实体OpenAppUser,一个是子实体OpenApp,我遇到的一个问题是,我须要保留OpenAppUser,然而须要一个OpenApp对象,我在前端传了一个id映射到OpenApp里了,然而在保留的时候appUserRepository.save(appUser);因为openApp是一个瞬态实体,会报找不到app_id异样(这是我app_user表里的app_id字段)。 起初我试了一下在save之前,我先应用appRepository.findById(appId);把openApp对象通过查问的形式变成了长久化实体,再去save(appUser);这时候是能够的。 不过看打印的sql日志,这种保留形式,在这之前都要进行查问,这里是依赖了利用,可能还有其余的,比方用户所属部门等等,那样的效率就低了,我想着在我保留的时候,我只须要传其余依赖的id就行了,不须要一个残缺的对象,我就开始看hibernate官网文档,发现了entityManager有个getReference(id)办法能够返回一个实体代理,获取这个实体代理的时候并不一定就会查询数据库。 而后我就发现spring data jpa的实现类里JpaRepository的getById(id)就是调用的entityManager.getReference(id),而后把appRepository.findById(appId)改成appRepository.getById(appId),再去看sql打印的时候,这次就没有再查问了,而是间接用的openApp代理实体里的id属性(我也只有这个属性,是通过前端传的)。这个插入效率就高很多了,一下子优化了查问。 spring data jpa 对于存在id保留,不存在id则新增的解决,有个AbstractPersistable类,能够通过设置它来实现,spring外面是通过判断id是不是null来走entityManager.persist还是merg的。

November 6, 2021 · 1 min · jiezi

关于spring-data-jpa:SpringData-JPA解决将Map等集合类型映射到数据库的问题

1.Map汇合映射到数据库借助AtrributeConverter目前我的需要是将汇合类中的信息存储到数据库中,例如有Map字段的formBody,最好是应用JSON格局,而SpringDateJpa也为咱们提供了相干的办法,通过自定义对象转换器,并通过注解的模式对须要转换的字段进行标注即可。该接口位于javax.persistence.AtrributeConverter。咱们通过自定义类实现该接口重写其中的办法实现须要的性能,本次具体的实现如下: import com.alibaba.fastjson.JSONObject;import javax.persistence.AttributeConverter;/** * @create :Created in 2021 * @description :Jpa映射转换器,存储时对象转JsonString, 读取时相同 */public class JpaConverterObjectJson implements AttributeConverter<Object, String> { @Override public String convertToDatabaseColumn(Object o) { return o==null?null: JSONObject.toJSONString(o); } @Override public Object convertToEntityAttribute(String s) { return s==null?null:JSONObject.parseObject(s); }}实体类中 @Entity@DynamicUpdate@Datapublic class Item extends BaseEntity{ /** 物品主键 */ @Id private Long itemId; /** 源物品信息 */ private String sourceItemId; /** 物品信息 */ @Convert(converter = JpaConverterObjectJson.class) @Column(columnDefinition = "TEXT") private Map<String,Object> itemInfo;}这样存的时候就是String,取出来主动为Map。 ...

January 28, 2021 · 1 min · jiezi

关于spring-data-jpa:SpringData-JPASpecification实现mysql中json字段查询

1.mysql中实现json字段查问select * from item where is_remove = 0 and json_extract(item_info,'$.GoodType') ='cat'2.JPA @Query实现mysql中json字段查问@Query(value = "select * from item where json_extract(item_info,?1) =?2 and item_id in (?3)",nativeQuery = true)List<Item> selectItems( String featuresKey,String featuresValue,List<Long> itemIds);List<Item> items = itemRepository.selectItemListByFeatures("$.GoodType","cat",ids);3.Specification实现mysql中json字段查问items=itemRepository.findAll(new Specification<Item>() { @Override public Predicate toPredicate(Root<Item> root, CriteriaQuery<?> query, CriteriaBuilder cb) { List<Predicate> list = new ArrayList<>(); for(Features fea:features){ if( fea.getFeaturesAliasName()!=null && fea.getFeaturesValue()!= null){ list.add(cb.equal( cb.function( "JSON_EXTRACT", String.class, root.get("itemInfo"), cb.literal("$."+fea.getFeaturesAliasName()) ), fea.getFeaturesValue()) ); } } list.add(cb.in(root.get("itemId")).value(itemIds)); Predicate[] p = new Predicate[list.size()]; query.where(cb.and(list.toArray(p))); return query.getGroupRestriction(); }});4.备注类Item{ "itemId":"1", "itemInfo":{"GoodId":"1002","GoodType":"cat","GoodName":"catyeall"}}Features{"featuresName":"GoodType","featuresValue":"cat"}5.附录深入浅出学Spring Data JPASpring Data JPA与PostgreSQL的jsonb类型集成与反对 ...

January 26, 2021 · 1 min · jiezi

关于spring-data-jpa:解决Spring中使用Example无法查询到Mongodb中的数据问题

1 问题形容在Spring Boot中应用Mongodb中的Example查问数据时查问不到,示例代码如下: ExampleMatcher matcher = ExampleMatcher.matching().withMatcher("username", ExampleMatcher.GenericPropertyMatchers.exact()).withIgnorePaths("id","password");2 问题剖析在Spring Data中应用Mongodb时,插入数据会增加一个_class字段,这个字段是用来映射POJO的,也就是说,如果一个实体类如下: @Document(collection = "user")class User{ @Id private String id; private String username; private String password;}则存进数据库的字段如下: _id,_class,username,password而应用ExampleMatcher,默认状况下会匹配所有字段,因而,如果实体类的包名扭转了,_class字段就不会匹配,这样就无奈正确地失去查问后果。 3 解决方案把_class增加进IgnorePath即可: .withIgnorePaths("_class","id","password")如果不想在插入数据时主动增加_class字段,能够批改MongoTemplate或者MappingMongoConverter,因为此超出本文范畴,仅给出参考链接,戳这里或这里。

December 11, 2020 · 1 min · jiezi

从零入门系列4Sprint-Boot-之-WEB接口设计实现

文章系列【从零入门系列-0】Sprint Boot 之 Hello World【从零入门系列-1】Sprint Boot 之 程序结构设计说明【从零入门系列-2】Sprint Boot 之 数据库实体类【从零入门系列-3】Sprint Boot 之 数据库操作类)前言前一章简述了已经实现了对数据库的增删改查以及复杂查询的功能,这一步将对相应的功能方法封装成WEB接口,对外提供WEB接口服务。 控制层类设计及测试控制层的角色是负责对访问路由到处理过程的关联映射和封装,在这里我们队Book新建一个控制类即可,在文件夹Controller上右键,New->Java Class新建BookController类。作为控制类,该类需要使用@Controller注解使之能够被识别为控制类对象,在这里我们使用@RestController,该注解包含了@Controller,相当于@Controller+@ResponseBody两个注解的结合,适合返回Json格式的控制器使用。 类定义@RestController@RequestMapping(path = "/library")public class BookController { @Autowired private BookJpaRepository bookJpaRepository; @Autowired private BookService bookService;}考虑到数据库的操作需要用到BookJpaRepository和BookService,这里首先声明这两个属性,并使用@Autowired注解自动装配。 在类上使用@RequestMapping(path = "/library")注解后,定义了该类的路径都是/library开始,可以统一接口路径,避免重复书写。 新增接口/*** 新增书籍* @param name* @param author* @param image* @return*/@PostMapping("/save")public Map<String, Object> save(@RequestParam String name, @RequestParam String author, @RequestParam String image){ Book book = new Book(); Map<String, Object> rsp = new HashMap<>(); book.setName(name); book.setAuthor(author); book.setImage(image); bookJpaRepository.save(book); rsp.put("data", book); rsp.put("code", "0"); rsp.put("info", "成功"); return rsp;}使用@PostMapping表示接口只接受POST请求,WEB接口路径为/library/save,该接口返回的是一个Map类型对象,但是由于类使用@RestController注解后,使得返回结果会自动转换成Json字符串格式。 ...

May 15, 2019 · 4 min · jiezi

从零入门系列3Sprint-Boot-之-数据库操作类

文章系列【从零入门系列-0】Sprint Boot 之 Hello World【从零入门系列-1】Sprint Boot 之 程序结构设计说明【从零入门系列-2】Sprint Boot 之 数据库实体类前言前一章简述了如何设计实现数据库实体类,本篇文章在此基础上进行开发,完成对该数据库表的常用操作,主要包括使用Spring Data JPA进行简单的增删改查和复杂查询操作。 Spring Data JPA是Spring提供的一套简化JPA开发的框架,按照约定好的【方法命名规则】写dao层接口,就可以在不写接口实现的情况下,实现对数据库的访问和操作,同时提供了很多除了CRUD之外的功能,如分页、排序、复杂查询等等,Spring Data JPA 可以理解为 JPA 规范的再次封装抽象,底层还是使用了 Hibernate 的 JPA 技术实现。通过引入Spring Data JPA后,我们可以基本不用写代码就能实现对数据库的增删改查操作。 此外,由于Spring Data JPA自带实现了很多内置的后台操作方法,因此在调用方法时必须根据其规范使用,深刻理解规范和约定。 表的基本操作实现(CRUD)在这里,先介绍一下JpaRepository,这是类型为interface的一组接口规范,是基于JPA的Repository接口,能够极大地减少访问数据库的代码编写,是实现Spring Data JPA技术访问数据库的关键接口。 编写数据操作接口在使用时,我们只需要定义一个继承该接口类型的接口即可实现对表的基本操作方法,在此我们需要对实体类Book进行操作,因此在Dao目录上右键New->Java Class,然后设置名称为BookJpaRepository,kind类型选Interface即可,然后添加注解及继承自JpaRepository,文件BookJpaRepository.java内容如下所示: package com.arbboter.demolibrary.Dao;import com.arbboter.demolibrary.Domain.Book;import org.springframework.data.jpa.repository.JpaRepository;import org.springframework.stereotype.Repository;@Repositorypublic interface BookJpaRepository extends JpaRepository<Book, Integer> {}@Repository持久层组件,用于标注数据访问组件,即DAO组件,此时配合上上一篇文章中的JPA配置,我们就可以进行增删改查啦,不用添加任何其他代码,因为JpaRepository已经帮我们实现好了。 编写测试用例代码打开框架自动生成的测试代码文件DemoLibraryApplicationTests.java编写测试用例,测试增删改查效果,测试代码如下: @RunWith(SpringRunner.class)@SpringBootTestpublic class DemoLibraryApplicationTests { /** * @Autowired 注释,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 * 通过 @Autowired的使用来消除 set ,get方法,简化程序代码 * 此处自动装配我们实现的BookJpaRepository接口,然后可以直接使用bookJpaRepository操作数据库 * 如果不加@Autowired,直接使用bookJpaRepository,程序运行会抛出异常 */ @Autowired private BookJpaRepository bookJpaRepository; @Test public void contextLoads() { Book book = new Book(); // 增 book.setName("Spring Boot 入门学习实践"); book.setAuthor("arbboter"); book.setImage("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2656353677,2997395625&fm=26&gp=0.jpg"); bookJpaRepository.save(book); System.out.println("保存数据成功:" + book); // 查 book = bookJpaRepository.findById(book.getId()).get(); System.out.println("新增后根据ID查询结果:" + book); // 修改 book.setName("Spring Boot 入门学习实践(修改版)"); bookJpaRepository.save(book); System.out.println("修改后根据ID查询结果:" + book); // 删除 bookJpaRepository.deleteById(book.getId()); }}注意在测试代码用需要对属性bookJpaRepository使用@Autowired 自动注入实现初始化,@Autowired 注解,它可以对类成员变量、方法及构造函数进行标注,完成自动装配的工作。 ...

May 14, 2019 · 5 min · jiezi

从零入门系列2Sprint-Boot-之-数据库实体类

文章系列【从零入门系列-0】Sprint Boot 之 Hello World【从零入门系列-1】Sprint Boot 之 程序结构设计说明前言本篇文章开始代码实践,系统设计从底向上展开,因此本篇先介绍如何实现数据库表实体类的设计实现。 SpringBoot数据库的持久层框架主要分为两种架构模式,即以JDBC Template为代表的SQL类和以Spring Data JPA为代表的ORM对象类。其中: Spring Data JPA 是 Spring 基于 ORM 框架、JPA 规范的基础上封装的一套JPA应用框架,可使开发者用极简的代码即可实现对数据的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展!学习并使用 Spring Data JPA 可以极大提高开发效率!spring data jpa让我们解脱了DAO层的操作,基本上所有CRUD都可以依赖于它来实现,自己写个仓储接口后继承JpaRepository即可实现最基本的增删改查功能!在使用@Entity进行实体类的持久化操作,当JPA检测到我们的实体类当中有@Entity 注解的时候,会在数据库中生成关联映射对应的表结构信息,因此针对本项目情况最底层的设计实现一个@Entity注解的书籍对象定义即可。 项目开始前,先按上一篇文章【从零入门系列-1】Sprint Boot 之 程序结构设计说明后台程序结构建立相对应的目录: 控制层:前端路由和后端处理关系处理,目录:Controller数据服务层:自定义对数据库的访问操作方法,目录:Service数据访问层:实现通用的数据库访问功能,SpringData的JPA方案,目录:Dao数据实体层:定义数据库表的属性方法,目录:Domain根据结构,我们需要在Domain目录下编写项目表实体类,右键Domain文件夹,New->Java Class。 编写实体类1.新建Book类 package com.arbboter.demolibrary.Domain; import javax.persistence.Entity; import javax.persistence.Table; /** * @Entity 注解该类为数据库表实体类,JPA可自动扫描识别到 * @Table 注解数据表信息,其中name指定表名 */ @Entity @Table(name = "library_book") public class Book{ }注意添加的类需要使用@Entity注解,其中@Table是可选的,默认不配置生成的表名和类名相同,如果上述类中不使用@Table,那么本类对应的表名为book。 2.添加表字段信息 public class Book{ /** * ID,唯一主键,按Alt+Enter可以快速导入引入 */ @Id @GeneratedValue private Integer id; /** * 书名 */ private String name; /** * 作者 */ private String author; /** * 封面 */ private String image; }@Id注解用于声明一个实体类的属性映射为数据库的主键列,该属性通常置于属性声明语句之前,可与声明语句同行,也可写在单独行上。 ...

May 14, 2019 · 2 min · jiezi

从零入门系列1Sprint-Boot-程序结构设计说明

【从零入门系列-1】Sprint Boot 程序结构设计说明文章系列【从零入门系列】Sprint Boot 之 Hello World设计效果图页面展示 增 删 改 查 搜索 页面程序结构图书馆结构分布图 1-WEB bootstrapbootstrap-table2-后台程序 控制层:前端路由和后端处理关系处理数据服务层:自定义对数据库的访问操作方法数据访问层:实现通用的数据库访问功能,SpringData的JPA方案数据实体层:定义数据库表的属性方法3-数据库 Book表:名字,作者,封面 说明程序的主要结构比较简单,主要难点在于初次接触很多东西理解不到位,各个层次间的交互链路以及Java基础薄弱,使得中间会有磕磕碰碰,且即使完成了整个项目,也不确定其中实现方案或者编码是否不合理。 但,先做出来再说,其他问题等以后熟练了再慢慢改进。 结束语本章预先提供了项目实际效果图以及项目的整体结构设计,后续文章会根据本篇章设计依次实现各个模块,请持续关注。

May 13, 2019 · 1 min · jiezi

Spring Data JPA设置表联合主键

遇到了一个新的问题,就是如何使用 Spring Data JPA 建立表的联合主键?网上找了很多答案,自己也踩了一些坑,总结出了三种方式,记录一下。第一种方式:第一种方式是直接使用 @Id 这个注解,来设置联合主键,例如下面这样,我要在 stu_no 和 stu_name 上建立联合主键:@Entity@Table(name = “student”)public class Student { @Id @Column(name = “stu_no”, nullable = false, length = 11) private Integer stuNo; @Id @Column(name = “stu_name”, nullable = false, length = 128) private String stuName; @Column(name = “stu_age”, nullable = false, length = 3) private Integer stuAge; @Column(name = “class_id”, nullable = false, length = 8) private String classId;}这种方式很简单,但是问题也是存在的。例如我想把 stu_no 设置为自动递增,加上 @GeneratedValue ,就是不生效,而是作用于 stu_name 这个字段上面,实在是很邪门!第二种方式:使用 @IdClass 这个注解来实现,我们需要新建一个主键类,这个类需要实现 Serializable 接口,并且需要无惨构造函数,还需要重写 equals 和 hashCode 方法,具体内容如下:@Data@Builder@NoArgsConstructor@AllArgsConstructor@EqualsAndHashCodepublic class StudentUPK implements Serializable { private Integer stuNo; private String stuName;}这里我是使用 lombok 插件来简化代码的,当然你可以自己手写构造函数和重写方法。然后实体类 Student 的内容如下:@Entity@IdClass(StudentUPK.class)@Table(name = “student”)public class Student { @Id @Column(name = “stu_no”, nullable = false, length = 11) private Integer stuNo; @Id @Column(name = “stu_name”, nullable = false, length = 128) private String stuName; @Column(name = “stu_age”, nullable = false, length = 3) private Integer stuAge; @Column(name = “class_id”, nullable = false, length = 8) private String classId;}这种方式也是存在问题的,在使用 JpaRepository 的时候,这样写:public interface StudentRepository extends JpaRepository<Student, StudentUPK> ,ID 写成这主键类,本以为是可以的,但一执行 CRUD 老是会报错,到现在还没找到原因。3. 第三种方式:第三种方式是使用 @Embeddable 和 @EmbeddedId 注解来实现,同样是需要一个主键类,内容如下:@Builder@Data@NoArgsConstructor@AllArgsConstructor@Embeddable@EqualsAndHashCodepublic class StudentUPK implements Serializable { @Column(name = “stu_no”, nullable = false, length = 11) private Integer stuNo; @Column(name = “stu_name”, nullable = false, length = 128) private String stuName;}主键类也是需要实现 Serializable 接口,并且需要无惨构造函数,还需要重写 equals 和 hashCode 方法。实体类 Student 的内容如下:@Entity@Table(name = “student”)public class Student { @EmbeddedId private StudentUPK studentUPK; @Column(name = “stu_age”, nullable = false, length = 3) private Integer stuAge; @Column(name = “class_id”, nullable = false, length = 8) private String classId;}这里使用了 @EmbeddedId 这个注解。这种方式使用 JpaRepository 是可以的,例如写成这样:public interface StudentRepository extends JpaRepository&lt;Student, StudentUPK&gt; {}然后再新建一个测试方法:@SpringBootTest@RunWith(SpringRunner.class)public class StudentRepositoryTest { @Resource private StudentRepository studentRepository; @Test public void testInsert(){ StudentUPK upk = StudentUPK.builder().stuNo(132).stuName(“Rose Duan”).build(); Student student = Student.builder().studentUPK(upk).stuAge(14).classId(“12312323”).build(); studentRepository.save(student); }}CRUD 的操作应该就成功了! ...

April 12, 2019 · 2 min · jiezi

Spring Data JPA 建立表的联合主键

最近遇到了一个小的问题,就是怎么使用 Spring Data JPA 建立表的联合主键?然后探索出了下面的两种方式。第一种方式:第一种方式是直接在类属性上面的两个字段都加上 @Id 注解,就像下面这样,给 stuNo 和 stuName 这两个字段加上联合主键:@Entity@Table(name = “student”)public class Student { @Id @Column(name = “stu_no”, nullable = false, length = 11) private Integer stuNo; @Id @Column(name = “stu_name”, nullable = false, length = 128) private String stuName; @Column(name = “stu_age”, nullable = false, length = 3) private Integer stuAge; @Column(name = “class_id”, nullable = false, length = 8) private String classId;}只不过需要注意的是,实体类需要实现 Serializable 接口。这种方式不是很好,虽然可以成功的创建表,但是使用 JpaRepository 的时候,需要指定主键 ID 的类型,这时候就会报错,所以使用第二种方式更好。第二种方式:实现起来也很简单,我们需要新建一个类,还是以 stuNo 和 stuName 建立联合主键,这个类需要实现 Serializable 接口。public class StudentUPK implements Serializable { private Integer stuNo; private String stuName;}然后在实体类 Student 上面加上 @IdClass 注解,两个字段上面还是加上 @Id 注解:@Entity@IdClass(StudentUPK.class)@Table(name = “student”)public class Student { @Id @Column(name = “stu_no”, nullable = false, length = 11) private Integer stuNo; @Id @Column(name = “stu_name”, nullable = false, length = 128) private String stuName; @Column(name = “stu_age”, nullable = false, length = 3) private Integer stuAge; @Column(name = “class_id”, nullable = false, length = 8) private String classId;}这样就能成功的创建表了,而且在使用 JpaRepoistory 的时候,可以指定主键为那个 StudentUPK 类,就像这样:public interface StudentRepository extends JpaRepository<Student, StudentUPK> 。 ...

April 12, 2019 · 1 min · jiezi

干货|一文读懂 Spring Data Jpa!

有很多读者留言希望松哥能好好聊聊 Spring Data Jpa!其实这个话题松哥以前零零散散的介绍过,在我的书里也有介绍过,但是在公众号中还没和大伙聊过,因此本文就和大家来仔细聊聊 Spring Data 和 Jpa!故事的主角Jpa1. JPA是什么Java Persistence API:用于对象持久化的 APIJava EE 5.0 平台标准的 ORM 规范,使得应用程序以统一的方式访问持久层2. JPA和Hibernate的关系JPA 是 Hibernate 的一个抽象(就像JDBC和JDBC驱动的关系);JPA 是规范:JPA 本质上就是一种 ORM 规范,不是ORM 框架,这是因为 JPA 并未提供 ORM 实现,它只是制订了一些规范,提供了一些编程的 API 接口,但具体实现则由 ORM 厂商提供实现;Hibernate 是实现:Hibernate 除了作为 ORM 框架之外,它也是一种 JPA 实现从功能上来说, JPA 是 Hibernate 功能的一个子集3. JPA的供应商JPA 的目标之一是制定一个可以由很多供应商实现的 API,Hibernate 3.2+、TopLink 10.1+ 以及 OpenJPA 都提供了 JPA 的实现,Jpa 供应商有很多,常见的有如下四种:HibernateJPA 的始作俑者就是 Hibernate 的作者,Hibernate 从 3.2 开始兼容 JPA。OpenJPAOpenJPA 是 Apache 组织提供的开源项目。TopLinkTopLink 以前需要收费,如今开源了。EclipseLink4. JPA的优势标准化: 提供相同的 API,这保证了基于JPA 开发的企业应用能够经过少量的修改就能够在不同的 JPA 框架下运行。简单易用,集成方便: JPA 的主要目标之一就是提供更加简单的编程模型,在 JPA 框架下创建实体和创建 Java 类一样简单,只需要使用 javax.persistence.Entity 进行注解;JPA 的框架和接口也都非常简单。可媲美JDBC的查询能力: JPA的查询语言是面向对象的,JPA定义了独特的JPQL,而且能够支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能够提供的高级查询特性,甚至还能够支持子查询。支持面向对象的高级特性: JPA 中能够支持面向对象的高级特性,如类之间的继承、多态和类之间的复杂关系,最大限度的使用面向对象的模型5. JPA包含的技术ORM 映射元数据:JPA 支持 XML 和 JDK 5.0 注解两种元数据的形式,元数据描述对象和表之间的映射关系,框架据此将实体对象持久化到数据库表中。JPA 的 API:用来操作实体对象,执行CRUD操作,框架在后台完成所有的事情,开发者从繁琐的 JDBC 和 SQL 代码中解脱出来。查询语言(JPQL):这是持久化操作中很重要的一个方面,通过面向对象而非面向数据库的查询语言查询数据,避免程序和具体的 SQL 紧密耦合。Spring DataSpring Data 是 Spring 的一个子项目。用于简化数据库访问,支持NoSQL 和 关系数据存储。其主要目标是使数据库的访问变得方便快捷。Spring Data 具有如下特点:SpringData 项目支持 NoSQL 存储: MongoDB (文档数据库) Neo4j(图形数据库) Redis(键/值存储) Hbase(列族数据库)SpringData 项目所支持的关系数据存储技术: JDBC JPASpring Data Jpa 致力于减少数据访问层 (DAO) 的开发量. 开发者唯一要做的,就是声明持久层的接口,其他都交给 Spring Data JPA 来帮你完成!框架怎么可能代替开发者实现业务逻辑呢?比如:当有一个 UserDao.findUserById() 这样一个方法声明,大致应该能判断出这是根据给定条件的 ID 查询出满足条件的 User 对象。Spring Data JPA 做的便是规范方法的名字,根据符合规范的名字来确定方法需要实现什么样的逻辑。主角的故事Jpa 的故事为了让大伙彻底把这两个东西学会,这里我就先来介绍单纯的Jpa使用,然后我们再结合 Spring Data 来看 Jpa如何使用。 整体步骤如下:使用 IntelliJ IDEA 创建项目,创建时选择 JavaEE Persistence ,如下:创建成功后,添加依赖jar,由于 Jpa 只是一个规范,因此我们说用Jpa实际上必然是用Jpa的某一种实现,那么是哪一种实现呢?当然就是Hibernate了,所以添加的jar,实际上来自 Hibernate,如下:添加实体类接下来在项目中添加实体类,如下:@Entity(name = “t_book”)public class Book { private Long id; private String name; private String author; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) public Long getId() { return id; } // 省略其他getter/setter}首先@Entity注解表示这是一个实体类,那么在项目启动时会自动针对该类生成一张表,默认的表名为类名,@Entity注解的name属性表示自定义生成的表名。@Id注解表示这个字段是一个id,@GeneratedValue注解表示主键的自增长策略,对于类中的其他属性,默认都会根据属性名在表中生成相应的字段,字段名和属性名相同,如果开发者想要对字段进行定制,可以使用@Column注解,去配置字段的名称,长度,是否为空等等。创建 persistence.xml 文件JPA 规范要求在类路径的 META-INF 目录下放置persistence.xml,文件的名称是固定的<?xml version=“1.0” encoding=“UTF-8”?><persistence xmlns=“http://java.sun.com/xml/ns/persistence" version=“2.0”> <persistence-unit name=“NewPersistenceUnit” transaction-type=“RESOURCE_LOCAL”> <provider>org.hibernate.jpa.HibernatePersistenceProvider</provider> <class>org.sang.Book</class> <properties> <property name=“hibernate.connection.url” value=“jdbc:mysql:///jpa01?useUnicode=true&amp;characterEncoding=UTF-8”/> <property name=“hibernate.connection.driver_class” value=“com.mysql.jdbc.Driver”/> <property name=“hibernate.connection.username” value=“root”/> <property name=“hibernate.connection.password” value=“123”/> <property name=“hibernate.archive.autodetection” value=“class”/> <property name=“hibernate.show_sql” value=“true”/> <property name=“hibernate.format_sql” value=“true”/> <property name=“hibernate.hbm2ddl.auto” value=“update”/> </properties> </persistence-unit></persistence>注意:persistence-unit 的name 属性用于定义持久化单元的名字, 必填。transaction-type:指定 JPA 的事务处理策略。RESOURCE_LOCAL:默认值,数据库级别的事务,只能针对一种数据库,不支持分布式事务。如果需要支持分布式事务,使用JTA:transaction-type=“JTA"class节点表示显式的列出实体类properties中的配置分为两部分:数据库连接信息以及Hibernate信息执行持久化操作EntityManagerFactory entityManagerFactory = Persistence.createEntityManagerFactory(“NewPersistenceUnit”);EntityManager manager = entityManagerFactory.createEntityManager();EntityTransaction transaction = manager.getTransaction();transaction.begin();Book book = new Book();book.setAuthor(“罗贯中”);book.setName(“三国演义”);manager.persist(book);transaction.commit();manager.close();entityManagerFactory.close();这里首先根据配置文件创建出来一个 EntityManagerFactory ,然后再根据 EntityManagerFactory 的实例创建出来一个 EntityManager ,然后再开启事务,调用 EntityManager 中的 persist 方法执行一次持久化操作,最后提交事务,执行完这些操作后,数据库中旧多出来一个 t_book 表,并且表中有一条数据。关于 JPQLJPQL语言,即 Java Persistence Query Language 的简称。JPQL 是一种和 SQL 非常类似的中间性和对象化查询语言,它最终会被编译成针对不同底层数据库的 SQL 查询,从而屏蔽不同数据库的差异。JPQL语言的语句可以是 select 语句、update 语句或delete语句,它们都通过 Query 接口封装执行。Query接口封装了执行数据库查询的相关方法。调用 EntityManager 的 createQuery、create NamedQuery 及 createNativeQuery 方法可以获得查询对象,进而可调用 Query 接口的相关方法来执行查询操作。Query接口的主要方法如下:int executeUpdate(); | 用于执行update或delete语句。List getResultList(); | 用于执行select语句并返回结果集实体列表。Object getSingleResult(); | 用于执行只返回单个结果实体的select语句。Query setFirstResult(int startPosition); | 用于设置从哪个实体记录开始返回查询结果。Query setMaxResults(int maxResult); | 用于设置返回结果实体的最大数。与setFirstResult结合使用可实现分页查询。Query setFlushMode(FlushModeType flushMode); | 设置查询对象的Flush模式。参数可以取2个枚举值:FlushModeType.AUTO 为自动更新数据库记录,FlushMode Type.COMMIT 为直到提交事务时才更新数据库记录。setHint(String hintName, Object value); | 设置与查询对象相关的特定供应商参数或提示信息。参数名及其取值需要参考特定 JPA 实现库提供商的文档。如果第二个参数无效将抛出IllegalArgumentException异常。setParameter(int position, Object value); | 为查询语句的指定位置参数赋值。Position 指定参数序号,value 为赋给参数的值。setParameter(int position, Date d, TemporalType type); | 为查询语句的指定位置参数赋 Date 值。Position 指定参数序号,value 为赋给参数的值,temporalType 取 TemporalType 的枚举常量,包括 DATE、TIME 及 TIMESTAMP 三个,,用于将 Java 的 Date 型值临时转换为数据库支持的日期时间类型(java.sql.Date、java.sql.Time及java.sql.Timestamp)。setParameter(int position, Calendar c, TemporalType type); | 为查询语句的指定位置参数赋 Calenda r值。position 指定参数序号,value 为赋给参数的值,temporalType 的含义及取舍同前。setParameter(String name, Object value); | 为查询语句的指定名称参数赋值。setParameter(String name, Date d, TemporalType type); | 为查询语句的指定名称参数赋 Date 值,用法同前。setParameter(String name, Calendar c, TemporalType type); | 为查询语句的指定名称参数设置Calendar值。name为参数名,其它同前。该方法调用时如果参数位置或参数名不正确,或者所赋的参数值类型不匹配,将抛出 IllegalArgumentException 异常。JPQL 举例和在 SQL 中一样,JPQL 中的 select 语句用于执行查询。其语法可表示为: select_clause form_clause [where_clause] [groupby_clause] [having_clause] [orderby_clause] 其中:from 子句是查询语句的必选子句。select 用来指定查询返回的结果实体或实体的某些属性。from 子句声明查询源实体类,并指定标识符变量(相当于SQL表的别名)。如果不希望返回重复实体,可使用关键字 distinct 修饰。select、from 都是 JPQL 的关键字,通常全大写或全小写,建议不要大小写混用。在 JPQL 中,查询所有实体的 JPQL 查询语句很简单,如下: select o from Order o 或 select o from Order as o 这里关键字 as 可以省去,标识符变量的命名规范与 Java 标识符相同,且区分大小写,调用 EntityManager 的 createQuery() 方法可创建查询对象,接着调用 Query 接口的 getResultList() 方法就可获得查询结果集,如下:Query query = entityManager.createQuery( “select o from Order o”); List orders = query.getResultList();Iterator iterator = orders.iterator();while(iterator.hasNext() ) { // 处理Order}其他方法的与此类似,这里不再赘述。Spring Data 的故事在 Spring Boot 中,Spring Data Jpa 官方封装了太多东西了,导致很多人用的时候不知道底层到底是怎么配置的,本文就和大伙来看看在手工的Spring环境下,Spring Data Jpa要怎么配置,配置完成后,用法和 Spring Boot 中的用法是一致的。基本环境搭建首先创建一个普通的Maven工程,并添加如下依赖:<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-oxm</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.27</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>5.0.2.RELEASE</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>5.2.12.Final</version> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-jpamodelgen</artifactId> <version>5.2.12.Final</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.0.29</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-jpa</artifactId> <version>1.11.3.RELEASE</version> </dependency></dependencies>这里除了 Jpa 的依赖之外,就是Spring Data Jpa 的依赖了。 接下来创建一个 User 实体类,创建方式参考 Jpa中实体类的创建方式,这里不再赘述。 接下来在resources目录下创建一个applicationContext.xml文件,并配置Spring和Jpa,如下:<context:property-placeholder location=“classpath:db.properties”/><context:component-scan base-package=“org.sang”/><bean class=“com.alibaba.druid.pool.DruidDataSource” id=“dataSource”> <property name=“driverClassName” value="${db.driver}”/> <property name=“url” value="${db.url}”/> <property name=“username” value="${db.username}"/> <property name=“password” value="${db.password}"/></bean><bean class=“org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean” id=“entityManagerFactory”> <property name=“dataSource” ref=“dataSource”/> <property name=“jpaVendorAdapter”> <bean class=“org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter”/> </property> <property name=“packagesToScan” value=“org.sang.model”/> <property name=“jpaProperties”> <props> <prop key=“hibernate.show_sql”>true</prop> <prop key=“hibernate.format_sql”>true</prop> <prop key=“hibernate.hbm2ddl.auto”>update</prop> <prop key=“hibernate.dialect”>org.hibernate.dialect.MySQL57Dialect</prop> </props> </property></bean><bean class=“org.springframework.orm.jpa.JpaTransactionManager” id=“transactionManager”> <property name=“entityManagerFactory” ref=“entityManagerFactory”/></bean><tx:annotation-driven transaction-manager=“transactionManager”/><!– 配置jpa –><jpa:repositories base-package=“org.sang.dao” entity-manager-factory-ref=“entityManagerFactory”/>这里和 Jpa 相关的配置主要是三个,一个是entityManagerFactory,一个是Jpa的事务,还有一个是配置dao的位置,配置完成后,就可以在 org.sang.dao 包下创建相应的 Repository 了,如下:public interface UserDao extends Repository<User, Long> { User getUserById(Long id);}getUserById表示根据id去查询User对象,只要我们的方法名称符合类似的规范,就不需要写SQL,具体的规范一会来说。好了,接下来,创建 Service 和 Controller 来调用这个方法,如下:@Service@Transactionalpublic class UserService { @Resource UserDao userDao; public User getUserById(Long id) { return userDao.getUserById(id); }}public void test1() { ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(“applicationContext.xml”); UserService userService = ctx.getBean(UserService.class); User user = userService.getUserById(1L); System.out.println(user);}这样,就可以查询到id为1的用户了。Repository上文我们自定义的 UserDao 实现了 Repository 接口,这个 Repository 接口是什么来头呢? 首先来看 Repository 的一个继承关系图: 可以看到,实现类不少。那么到底如何理解 Repository 呢?Repository 接口是 Spring Data 的一个核心接口,它不提供任何方法,开发者需要在自己定义的接口中声明需要的方法 public interface Repository<T, ID extends Serializable> { }若我们定义的接口继承了 Repository, 则该接口会被 IOC 容器识别为一个 Repository Bean,进而纳入到 IOC 容器中,进而可以在该接口中定义满足一定规范的方法。Spring Data可以让我们只定义接口,只要遵循 Spring Data 的规范,就无需写实现类。与继承 Repository 等价的一种方式,就是在持久层接口上使用 @RepositoryDefinition 注解,并为其指定 domainClass 和 idClass 属性。像下面这样:@RepositoryDefinition(domainClass = User.class, idClass = Long.class)public interface UserDao{ User findById(Long id); List<User> findAll();}基础的 Repository 提供了最基本的数据访问功能,其几个子接口则扩展了一些功能,它的几个常用的实现类如下:CrudRepository: 继承 Repository,实现了一组 CRUD 相关的方法PagingAndSortingRepository: 继承 CrudRepository,实现了一组分页排序相关的方法JpaRepository: 继承 PagingAndSortingRepository,实现一组 JPA 规范相关的方法自定义的 XxxxRepository 需要继承 JpaRepository,这样的 XxxxRepository 接口就具备了通用的数据访问控制层的能力。JpaSpecificationExecutor: 不属于Repository体系,实现一组 JPA Criteria 查询相关的方法方法定义规范1.简单条件查询按照 Spring Data 的规范,查询方法以 find | read | get 开头涉及条件查询时,条件的属性用条件关键字连接,要注意的是:条件属性以首字母大写例如:定义一个 Entity 实体类:class User{ private String firstName; private String lastName; } 使用And条件连接时,条件的属性名称与个数要与参数的位置与个数一一对应,如下:findByLastNameAndFirstName(String lastName,String firstName);支持属性的级联查询. 若当前类有符合条件的属性, 则优先使用, 而不使用级联属性. 若需要使用级联属性, 则属性之间使用 _ 进行连接.查询举例:按照id查询User getUserById(Long id);User getById(Long id);查询所有年龄小于90岁的人List<User> findByAgeLessThan(Long age);查询所有姓赵的人List<User> findByUsernameStartingWith(String u);查询所有姓赵的、并且id大于50的人List<User> findByUsernameStartingWithAndIdGreaterThan(String name, Long id);查询所有姓名中包含"上"字的人List<User> findByUsernameContaining(String name);查询所有姓赵的或者年龄大于90岁的List<User> findByUsernameStartingWithOrAgeGreaterThan(String name, Long age);查询所有角色为1的用户List<User> findByRole_Id(Long id);2.支持的关键字支持的查询关键字如下图: 3.查询方法流程解析为什么写上方法名,JPA就知道你想干嘛了呢?假如创建如下的查询:findByUserDepUuid(),框架在解析该方法时,首先剔除 findBy,然后对剩下的属性进行解析,假设查询实体为Doc:先判断 userDepUuid (根据 POJO 规范,首字母变为小写)是否为查询实体的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,继续第二步;从右往左截取第一个大写字母开头的字符串(此处为Uuid),然后检查剩下的字符串是否为查询实体的一个属性,如果是,则表示根据该属性进行查询;如果没有该属性,则重复第二步,继续从右往左截取;最后假设 user 为查询实体的一个属性;接着处理剩下部分(DepUuid),先判断 user 所对应的类型是否有depUuid属性,如果有,则表示该方法最终是根据 “ Doc.user.depUuid” 的取值进行查询;否则继续按照步骤 2 的规则从右往左截取,最终表示根据 “Doc.user.dep.uuid” 的值进行查询。可能会存在一种特殊情况,比如 Doc包含一个 user 的属性,也有一个 userDep 属性,此时会存在混淆。可以明确在属性之间加上 “_” 以显式表达意图,比如 “findByUser_DepUuid()” 或者 “findByUserDep_uuid()“还有一些特殊的参数:例如分页或排序的参数:Page<UserModel> findByName(String name, Pageable pageable); List<UserModel> findByName(String name, Sort sort);@Query注解有的时候,这里提供的查询关键字并不能满足我们的查询需求,这个时候就可以使用 @Query 关键字,来自定义查询 SQL,例如查询Id最大的User:@Query(“select u from t_user u where id=(select max(id) from t_user)")User getMaxIdUser();如果查询有参数的话,参数有两种不同的传递方式,利用下标索引传参,索引参数如下所示,索引值从1开始,查询中 ”?X” 个数需要与方法定义的参数个数相一致,并且顺序也要一致:@Query(“select u from t_user u where id>?1 and username like ?2”)List<User> selectUserByParam(Long id, String name);命名参数(推荐):这种方式可以定义好参数名,赋值时采用@Param(“参数名”),而不用管顺序:@Query(“select u from t_user u where id>:id and username like :name”)List<User> selectUserByParam2(@Param(“name”) String name, @Param(“id”) Long id);查询时候,也可以是使用原生的SQL查询,如下:@Query(value = “select * from t_user”,nativeQuery = true)List<User> selectAll();@Modifying注解涉及到数据修改操作,可以使用 @Modifying 注解,@Query 与 @Modifying 这两个 annotation一起声明,可定义个性化更新操作,例如涉及某些字段更新时最为常用,示例如下:@Modifying@Query(“update t_user set age=:age where id>:id”)int updateUserById(@Param(“age”) Long age, @Param(“id”) Long id);注意:可以通过自定义的 JPQL 完成 UPDATE 和 DELETE 操作. 注意: JPQL 不支持使用 INSERT方法的返回值应该是 int,表示更新语句所影响的行数在调用的地方必须加事务,没有事务不能正常执行默认情况下, Spring Data 的每个方法上有事务, 但都是一个只读事务. 他们不能完成修改操作说到这里,再来顺便说说Spring Data 中的事务问题:Spring Data 提供了默认的事务处理方式,即所有的查询均声明为只读事务。对于自定义的方法,如需改变 Spring Data 提供的事务默认方式,可以在方法上添加 @Transactional 注解。进行多个 Repository 操作时,也应该使它们在同一个事务中处理,按照分层架构的思想,这部分属于业务逻辑层,因此,需要在Service 层实现对多个 Repository 的调用,并在相应的方法上声明事务。好了,关于Spring Data Jpa 本文就先说这么多,这一块,松哥有一些私藏多年的笔记和视频,如下图: 那么这些资料如何获取呢?以下两个条件满足其一即可:关注牧码小子公众号,将本文对应的公众号文章分享到朋友圈,不可以设置分组,三天后截图发给松哥,资料免费送你。关注牧码小子公众号,将本文对应的公众号文章分享到一个超过200人的微信群中(QQ群不算,松哥是群主的微信群也不算,群要为Java方向的群),如果没有这么大的群,也可以分享到多个Java方向的微信群,群累计人数达到200即可。以上条件满足其一,加松哥微信,给你资料。 更多微服务资料,请关注公众号牧码小子,关注后回复 Java,领取松哥为大伙精心准备的 Java 干货! ...

April 2, 2019 · 5 min · jiezi

Spring Data JPA 必须掌握的 20+ 个查询关键字

微信公众号:一个优秀的废人如有问题或建议,请后台留言,我会尽力解决你的问题。前言又是小师弟的投稿,确是一个喜欢技术的朋友。以下为原文:今天闲的无聊看 Spring Data JPA 官方文档的时候,发现并没有完整的 Jpa 关键字语义翻译。所以今天写了一篇中文文档,如果有错误,望大家轻喷。以下为官方图片以及示例代码和注释 :首先参照官方文档创建指定数据库CREATE TABLE demo_jpa ( id int(11) NOT NULL AUTO_INCREMENT, first_name varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, last_name varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, sex varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, email varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, age int(12) NOT NULL, PRIMARY KEY (id) USING BTREE) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;示例代码及注释<参照以上顺序>/** * @Author: EvilSay * @Date: 2019/2/25 16:15 /public interface DemoJpaRepositories extends JpaRepository<DemoJpa,Integer> { //根据firstName与LastName查找(两者必须在数据库有) DemoJpa findByFirstNameAndLastName(String firstName, String lastName); //根据firstName或LastName查找(两者其一有就行) DemoJpa findByLastNameOrFirstName(String lastName,String firstName); //根据firstName查找它是否存在数据库里<类似与以下关键字> //DemoJpa findByFirstName(String firstName); DemoJpa findByFirstNameIs(String firstName); //在Age数值age到age2之间的数据 List<DemoJpa> findByAgeBetween(Integer age, Integer age2); //小于指定age数值之间的数据 List<DemoJpa> findByAgeLessThan(Integer age); //小于等于指定age数值的数据 List<DemoJpa> findByAgeLessThanEqual(Integer age); //大于指定age数值之间的数据 List<DemoJpa> findByAgeGreaterThan(Integer age); //大于或等于指定age数值之间的数据 List<DemoJpa> findByAgeGreaterThanEqual(Integer age); //在指定age数值之前的数据类似关键字<LessThan> List<DemoJpa> findByAgeAfter(Integer age); //在指定age数值之后的数据类似关键字<GreaterThan> List<DemoJpa> findByAgeBefore(Integer age); //返回age字段为空的数据 List<DemoJpa> findByAgeIsNull(); //返回age字段不为空的数据 List<DemoJpa> findByAgeNotNull(); /* * 该关键字我一度以为是类似数据库的模糊查询, * 但是我去官方文档看到它里面并没有通配符。 * 所以我觉得它类似 * DemoJpa findByFirstName(String firstName); * @see https://docs.spring.io/spring-data/jpa/docs/2.1.5.RELEASE/reference/html/#jpa.repositories */ DemoJpa findByFirstNameLike(String firstName); //同上 List<DemoJpa> findByFirstNameNotLike(String firstName); //查找数据库中指定类似的名字(如:输入一个名字"M" Jpa会返回多个包含M开头的名字的数据源)<类似数据库模糊查询> List<DemoJpa> findByFirstNameStartingWith(String firstName); //查找数据库中指定不类似的名字(同上) List<DemoJpa> findByFirstNameEndingWith(String firstName); //查找包含的指定数据源(这个与以上两个字段不同的地方在与它必须输入完整的数据才可以查询) List<DemoJpa> findByFirstNameContaining(String firstName); //根据age选取所有的数据源并按照LastName进行升序排序 List<DemoJpa> findByAgeOrderByLastName(Integer age); //返回不是指定age的所有数据 List<DemoJpa> findByAgeNot(Integer age); //查找包含多个指定age返回的数据 List<DemoJpa> findByAgeIn(List<Integer> age);}单元测试<已经全部通过>@SpringBootTest@RunWith(SpringRunner.class)@Slf4jpublic class DemoJpaRepositoriesTest { @Autowired private DemoJpaRepositories repositories; @Test public void findByFirstNameAndLastName() { DemoJpa demoJpa = repositories.findByFirstNameAndLastName(“May”, “Eden”); Assert.assertEquals(demoJpa.getFirstName(),“May”); } @Test public void findByLastNameOrFirstName() { DemoJpa demoJpa = repositories.findByLastNameOrFirstName(“Geordie”, “Eden”); Assert.assertNotEquals(demoJpa.getLastName(),“Eden”); } @Test public void findByFirstNameIs() { DemoJpa demoJpa = repositories.findByFirstNameIs(“amy”); Assert.assertNull(demoJpa); } @Test public void findByAgeBetween() { List<DemoJpa> demoJpaList = repositories.findByAgeBetween(15, 17); Assert.assertEquals(3,demoJpaList.size()); } @Test public void findByAgeLessThan() { List<DemoJpa> demoJpaList = repositories.findByAgeLessThan(17); Assert.assertEquals(2,demoJpaList.size()); } @Test public void findByAgeLessThanEqual() { List<DemoJpa> demoJpaList = repositories.findByAgeLessThanEqual(17); Assert.assertEquals(3,demoJpaList.size()); } @Test public void findByAgeGreaterThan() { List<DemoJpa> demoJpaList = repositories.findByAgeGreaterThan(17); Assert.assertEquals(2,demoJpaList.size()); } @Test public void findByAgeGreaterThanEqual() { List<DemoJpa> demoJpaList = repositories.findByAgeGreaterThanEqual(17); Assert.assertEquals(3,demoJpaList.size()); } @Test public void findByAgeAfter() { List<DemoJpa> demoJpaList = repositories.findByAgeAfter(17); Assert.assertEquals(2,demoJpaList.size()); } @Test public void findByAgeBefore() { List<DemoJpa> demoJpaList = repositories.findByAgeBefore(17); Assert.assertEquals(2,demoJpaList.size()); } @Test public void findByAgeIsNull() { List<DemoJpa> demoJpaList = repositories.findByAgeIsNull(); Assert.assertEquals(0,demoJpaList.size()); } @Test public void findByAgeNotNull() { List<DemoJpa> demoJpaList = repositories.findByAgeNotNull(); Assert.assertEquals(5,demoJpaList.size()); } @Test public void findByFirstNameLike() { DemoJpa demoJpa = repositories.findByFirstNameLike(“May”); Assert.assertNotNull(demoJpa); } @Test public void findByFirstNameNotLike() { } @Test public void findByFirstNameStartingWith() { List<DemoJpa> demoJpaList = repositories.findByFirstNameStartingWith(“May”); Assert.assertEquals(2,demoJpaList.size()); } @Test public void findByFirstNameEndingWith() { List<DemoJpa> demoJpaList = repositories.findByFirstNameEndingWith(“Evil”); Assert.assertEquals(0,demoJpaList.size()); } @Test public void findByFirstNameContaining() { List<DemoJpa> demoJpaList = repositories.findByFirstNameContaining(“hack”); Assert.assertEquals(0,demoJpaList.size()); } @Test public void findByAgeOrderByLastName() { List<DemoJpa> demoJpaList = repositories.findByAgeOrderByLastName(18); for (DemoJpa demoJpaL : demoJpaList){ log.info(“数据结果”+demoJpaL.toString()); } } @Test public void findByAgeNot() { List<DemoJpa> demoJpaList = repositories.findByAgeNot(20); Assert.assertEquals(5,demoJpaList.size()); } @Test public void findByAgeIn() { List<DemoJpa> demoJpaList = repositories.findByAgeIn(Arrays.asList(15, 16)); Assert.assertEquals(2,demoJpaList.size()); }}后语如果本文对你哪怕有一丁点帮助,请帮忙点好看。你的好看是我坚持写作的动力。另外,关注之后在发送 1024 可领取免费学习资料。资料内容详情请看这篇旧文:Python、C++、Java、Linux、Go、前端、算法资料分享 ...

February 27, 2019 · 2 min · jiezi

Spring Data JPA REST Query Criteria

案例概述在本系列的第一篇文章中,我们将探索一种用于REST API的简单查询语言。我们将充分利用Spring作为REST API,并将JPA 2标准用于持久性方面。为什么使用查询语言?因为 - 对于任何复杂的API - 通过非常简单的字段搜索/过滤资源是不够的。查询语言更灵活,允许您精确过滤所需的资源。User Entity首先 - 让我们提出我们将用于过滤器/搜索API的简单实体 - 一个基本用户:@Entitypublic class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String firstName; private String lastName; private String email; private int age;}使用CriteriaBuilder进行过滤现在 - 让我们深入研究问题 - 持久层中的查询。构建查询抽象是一个平衡问题。一方面我们需要很大的灵活性,另一方面我们需要保持复杂性可管理性。高级别,功能很简单 - 你传递一些约束,你会得到一些结果。让我们看看它是如何工作的:@Repositorypublic class UserDAO implements IUserDAO { @PersistenceContext private EntityManager entityManager; @Override public List<User> searchUser(List<SearchCriteria> params) { CriteriaBuilder builder = entityManager.getCriteriaBuilder(); CriteriaQuery<User> query = builder.createQuery(User.class); Root r = query.from(User.class); Predicate predicate = builder.conjunction(); for (SearchCriteria param : params) { if (param.getOperation().equalsIgnoreCase(">")) { predicate = builder.and(predicate, builder.greaterThanOrEqualTo(r.get(param.getKey()), param.getValue().toString())); } else if (param.getOperation().equalsIgnoreCase("<")) { predicate = builder.and(predicate, builder.lessThanOrEqualTo(r.get(param.getKey()), param.getValue().toString())); } else if (param.getOperation().equalsIgnoreCase(":")) { if (r.get(param.getKey()).getJavaType() == String.class) { predicate = builder.and(predicate, builder.like(r.get(param.getKey()), "%" + param.getValue() + “%”)); } else { predicate = builder.and(predicate, builder.equal(r.get(param.getKey()), param.getValue())); } } } query.where(predicate); List<User> result = entityManager.createQuery(query).getResultList(); return result; } @Override public void save(User entity) { entityManager.persist(entity); }}如您所见,searchUser API获取非常简单的约束列表,根据这些约束组成查询,执行搜索并返回结果。约束类也很简单:public class SearchCriteria { private String key; private String operation; private Object value;}该SearchCriteria实现持有我们的查询参数:key:用于保存字段名称 - 例如:firstName,age,…等。operation:用于保持操作 - 例如:Equality,less,…等。value:用于保存字段值 - 例如:john,25,…等。测试搜索查询现在 - 让我们测试我们的搜索机制,以确保它可用。首先 - 让我们通过添加两个用户来初始化我们的数据库以进行测试 - 如下例所示:@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = { PersistenceConfig.class })@Transactional@TransactionConfigurationpublic class JPACriteriaQueryTest { @Autowired private IUserDAO userApi; private User userJohn; private User userTom; @Before public void init() { userJohn = new User(); userJohn.setFirstName(“John”); userJohn.setLastName(“Doe”); userJohn.setEmail(“john@doe.com”); userJohn.setAge(22); userApi.save(userJohn); userTom = new User(); userTom.setFirstName(“Tom”); userTom.setLastName(“Doe”); userTom.setEmail(“tom@doe.com”); userTom.setAge(26); userApi.save(userTom); }}现在,让我们得到一个具有特定firstName和lastName的用户 - 如下例所示:@Testpublic void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() { List<SearchCriteria> params = new ArrayList<SearchCriteria>(); params.add(new SearchCriteria(“firstName”, “:”, “John”)); params.add(new SearchCriteria(“lastName”, “:”, “Doe”)); List<User> results = userApi.searchUser(params); assertThat(userJohn, isIn(results)); assertThat(userTom, not(isIn(results)));}接下来,让我们得到一个具有相同lastName的用户列表:@Testpublic void givenLast_whenGettingListOfUsers_thenCorrect() { List<SearchCriteria> params = new ArrayList<SearchCriteria>(); params.add(new SearchCriteria(“lastName”, “:”, “Doe”)); List<User> results = userApi.searchUser(params); assertThat(userJohn, isIn(results)); assertThat(userTom, isIn(results));}接下来,让age大于或等于25的用户:@Testpublic void givenLastAndAge_whenGettingListOfUsers_thenCorrect() { List<SearchCriteria> params = new ArrayList<SearchCriteria>(); params.add(new SearchCriteria(“lastName”, “:”, “Doe”)); params.add(new SearchCriteria(“age”, “>”, “25”)); List<User> results = userApi.searchUser(params); assertThat(userTom, isIn(results)); assertThat(userJohn, not(isIn(results)));}接下来,让我们搜索实际不存在的用户:@Testpublic void givenWrongFirstAndLast_whenGettingListOfUsers_thenCorrect() { List<SearchCriteria> params = new ArrayList<SearchCriteria>(); params.add(new SearchCriteria(“firstName”, “:”, “Adam”)); params.add(new SearchCriteria(“lastName”, “:”, “Fox”)); List<User> results = userApi.searchUser(params); assertThat(userJohn, not(isIn(results))); assertThat(userTom, not(isIn(results)));}最后,让我们搜索仅给出部分firstName的用户:@Testpublic void givenPartialFirst_whenGettingListOfUsers_thenCorrect() { List<SearchCriteria> params = new ArrayList<SearchCriteria>(); params.add(new SearchCriteria(“firstName”, “:”, “jo”)); List<User> results = userApi.searchUser(params); assertThat(userJohn, isIn(results)); assertThat(userTom, not(isIn(results)));}UserController最后,让我们现在将这种灵活搜索的持久性支持连接到我们的REST API。我们将设置一个简单的UserController - 使用findAll()使用“search”传递整个搜索/过滤器表达式:@Controllerpublic class UserController { @Autowired private IUserDao api; @RequestMapping(method = RequestMethod.GET, value = “/users”) @ResponseBody public List<User> findAll(@RequestParam(value = “search”, required = false) String search) { List<SearchCriteria> params = new ArrayList<SearchCriteria>(); if (search != null) { Pattern pattern = Pattern.compile("(\w+?)(:|<|>)(\w+?),"); Matcher matcher = pattern.matcher(search + “,”); while (matcher.find()) { params.add(new SearchCriteria(matcher.group(1), matcher.group(2), matcher.group(3))); } } return api.searchUser(params); }}请注意我们如何简单地从搜索表达式中创建搜索条件对象。我们现在正处于开始使用API并确保一切正常工作的地步:http://localhost:8080/users?search=lastName:doe,age>25这是它的回应:[{ “id”:2, “firstName”:“tom”, “lastName”:“doe”, “email”:“tom@doe.com”, “age”:26}]案例结论这个简单而强大的实现支持对REST API进行相当多的智能过滤。是的—它仍然很粗糙,可以改进(下一篇文章将对此进行改进)—但它是在api上实现这种过滤功能的坚实起点。 ...

January 23, 2019 · 2 min · jiezi

Spring Data JPA REST Query QueryDSL

案例概述在本教程中,我们将研究使用Spring Data JPA和Querydsl为REST API构建查询语言。在本系列的前两篇文章中,我们使用JPA Criteria和Spring Data JPA规范构建了相同的搜索/过滤功能。那么 - 为什么要使用查询语言?因为 - 对于任何复杂的API来说 - 通过非常简单的字段搜索/过滤资源是不够的。查询语言更灵活,允许您精确过滤所需的资源。Querydsl配置首先 - 让我们看看如何配置我们的项目以使用Querydsl。我们需要将以下依赖项添加到pom.xml:<dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-apt</artifactId> <version>4.1.4</version> </dependency><dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-jpa</artifactId> <version>4.1.4</version> </dependency>我们还需要配置APT - Annotation处理工具 - 插件如下:<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.mysema.query.apt.jpa.JPAAnnotationProcessor</processor> </configuration> </execution> </executions></plugin>MyUser Entity接下来 - 让我们看一下我们将在Search API中使用的“MyUser”实体:@Entitypublic class MyUser { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; private String firstName; private String lastName; private String email; private int age;}使用PathBuilder自定义Predicate现在 - 让我们根据一些任意约束创建一个自定义Predicate。我们在这里使用PathBuilder而不是自动生成的Q类型,因为我们需要动态创建路径以获得更抽象的用法:public class MyUserPredicate { private SearchCriteria criteria; public BooleanExpression getPredicate() { PathBuilder<MyUser> entityPath = new PathBuilder<>(MyUser.class, “user”); if (isNumeric(criteria.getValue().toString())) { NumberPath<Integer> path = entityPath.getNumber(criteria.getKey(), Integer.class); int value = Integer.parseInt(criteria.getValue().toString()); switch (criteria.getOperation()) { case “:”: return path.eq(value); case “>”: return path.goe(value); case “<”: return path.loe(value); } } else { StringPath path = entityPath.getString(criteria.getKey()); if (criteria.getOperation().equalsIgnoreCase(":")) { return path.containsIgnoreCase(criteria.getValue().toString()); } } return null; }}请注意Predicate的实现是通常如何处理多种类型的操作。这是因为查询语言根据定义是一种开放式语言,您可以使用任何支持的操作对任何字段进行过滤。为了表示这种开放式过滤标准,我们使用了一个简单但非常灵活的实现 - SearchCriteria:public class SearchCriteria { private String key; private String operation; private Object value;}key:用于保存字段名称 - 例如:firstName,age,…等。operation:用于保持操作 - 例如:Equality,less,…等。value:用于保存字段值 - 例如:john,25,…等。MyUserRepository现在 - 让我们来看看我们的MyUserRepository。我们需要MyUserRepository来扩展QueryDslPredicateExecutor,以便我们以后可以使用Predicates来过滤搜索结果:public interface MyUserRepository extends JpaRepository<MyUser, Long>, QueryDslPredicateExecutor<MyUser>, QuerydslBinderCustomizer<QMyUser> { @Override default public void customize( QuerydslBindings bindings, QMyUser root) { bindings.bind(String.class) .first((SingleValueBinding<StringPath, String>) StringExpression::containsIgnoreCase); bindings.excluding(root.email); }}结合Predicates接下来让我们看看组合Predicates在结果过滤中使用多个约束。在以下示例中 - 我们使用构建器 - MyUserPredicatesBuilder - 来组合Predicates:public class MyUserPredicatesBuilder { private List<SearchCriteria> params; public MyUserPredicatesBuilder() { params = new ArrayList<>(); } public MyUserPredicatesBuilder with( String key, String operation, Object value) { params.add(new SearchCriteria(key, operation, value)); return this; } public BooleanExpression build() { if (params.size() == 0) { return null; } List<BooleanExpression> predicates = new ArrayList<>(); MyUserPredicate predicate; for (SearchCriteria param : params) { predicate = new MyUserPredicate(param); BooleanExpression exp = predicate.getPredicate(); if (exp != null) { predicates.add(exp); } } BooleanExpression result = predicates.get(0); for (int i = 1; i < predicates.size(); i++) { result = result.and(predicates.get(i)); } return result; }}测试搜索查询接下来 - 让我们测试一下我们的Search API。我们将首先使用少数用户初始化数据库 - 准备好这些数据并进行测试:@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration(classes = { PersistenceConfig.class })@Transactional@Rollbackpublic class JPAQuerydslIntegrationTest { @Autowired private MyUserRepository repo; private MyUser userJohn; private MyUser userTom; @Before public void init() { userJohn = new MyUser(); userJohn.setFirstName(“John”); userJohn.setLastName(“Doe”); userJohn.setEmail(“john@doe.com”); userJohn.setAge(22); repo.save(userJohn); userTom = new MyUser(); userTom.setFirstName(“Tom”); userTom.setLastName(“Doe”); userTom.setEmail(“tom@doe.com”); userTom.setAge(26); repo.save(userTom); }}接下来,让我们看看如何查找具有给定姓氏的用户:@Testpublic void givenLast_whenGettingListOfUsers_thenCorrect() { MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder().with(“lastName”, “:”, “Doe”); Iterable<MyUser> results = repo.findAll(builder.build()); assertThat(results, containsInAnyOrder(userJohn, userTom));}现在,让我们看看如何找到具有名字和姓氏的用户:@Testpublic void givenFirstAndLastName_whenGettingListOfUsers_thenCorrect() { MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder() .with(“firstName”, “:”, “John”).with(“lastName”, “:”, “Doe”); Iterable<MyUser> results = repo.findAll(builder.build()); assertThat(results, contains(userJohn)); assertThat(results, not(contains(userTom)));}接下来,让我们看看如何找到具有姓氏和最小年龄的用户@Testpublic void givenLastAndAge_whenGettingListOfUsers_thenCorrect() { MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder() .with(“lastName”, “:”, “Doe”).with(“age”, “>”, “25”); Iterable<MyUser> results = repo.findAll(builder.build()); assertThat(results, contains(userTom)); assertThat(results, not(contains(userJohn)));}接下来,让我们搜索实际不存在的用户:@Testpublic void givenWrongFirstAndLast_whenGettingListOfUsers_thenCorrect() { MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder() .with(“firstName”, “:”, “Adam”).with(“lastName”, “:”, “Fox”); Iterable<MyUser> results = repo.findAll(builder.build()); assertThat(results, emptyIterable());}最后 - 让我们看看如何找到仅给出名字的一部分的MyUser - 如下例所示:@Testpublic void givenPartialFirst_whenGettingListOfUsers_thenCorrect() { MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder().with(“firstName”, “:”, “jo”); Iterable<MyUser> results = repo.findAll(builder.build()); assertThat(results, contains(userJohn)); assertThat(results, not(contains(userTom)));}UserController最后,让我们将所有内容放在一起并构建REST API。我们定义了一个UserController,它定义了一个带有“search”参数的简单方法findAll()来传递查询字符串:@Controllerpublic class UserController { @Autowired private MyUserRepository myUserRepository; @RequestMapping(method = RequestMethod.GET, value = “/myusers”) @ResponseBody public Iterable<MyUser> search(@RequestParam(value = “search”) String search) { MyUserPredicatesBuilder builder = new MyUserPredicatesBuilder(); if (search != null) { Pattern pattern = Pattern.compile("(\w+?)(:|<|>)(\w+?),"); Matcher matcher = pattern.matcher(search + “,”); while (matcher.find()) { builder.with(matcher.group(1), matcher.group(2), matcher.group(3)); } } BooleanExpression exp = builder.build(); return myUserRepository.findAll(exp); }}这是一个快速测试URL示例:http://localhost:8080/myusers?search=lastName:doe,age>25回应:[{ “id”:2, “firstName”:“tom”, “lastName”:“doe”, “email”:“tom@doe.com”, “age”:26}]案例结论第三篇文章介绍了为REST API构建查询语言的第一步,充分利用了Querydsl库。 ...

January 23, 2019 · 3 min · jiezi