【方向盘】版本历史&代码示例之:Bean Validation、JPA

本文转载自微信公众号「Java方向盘」,作者方向盘 。转载本文请联系Java方向盘公众号。

10年积累的网站设计、成都网站制作经验,可以快速应对客户对网站的新想法和需求。提供各种问题对应的解决方案。让选择我们的客户得到更好、更有力的网络服务。我虽然不认识你,你也不认识我。但先网站制作后付款的网站建设流程,更有东风免费网站建设让你可以放心的选择与我们合作。

正文

Bean Validation

数据校验:在任何时候,当你想要处理一个应用程序的逻辑时,确保数据的正确性是你必须要考虑和面对的事情。也就说我们必须通过某种手段,确保输入进来的数据是正确的。

然而,应用程序一般是分层的,同样的验证逻辑往往会出现在不同的层,这样就会给代码组织管理上带来冗余负担。为了避免这类情况的发生,最好就是做一层抽象:将验证逻辑与响应的模型进行绑定,这就是Bean Validation。

Bean Validation简直就是业务开发中祛掉坏味道代码的利器,完美的实现契约式编程,大大提高开发效率,降低出错概率。

注意:Bean Validation它是一种通用规范,并不只属于Web层技术,即使大概率你可能只在Spring MVC中使用过它~

 
 
 
 
  1.  
  2.  
  3.     javax.validation 
  4.     validation-api 
  5.     2.0.1.Final 
  6.  
  7.  
  8.  
  9.  
  10.     jakarta.validation 
  11.     jakarta.validation-api 
  12.     3.0.0 
  13.      
  14.  

版本历程

版本发布日期JSR版本对应Java EE版本主要特性
1.02009.11JSR 303Java EE 6对JavaBean进行验证,提供13个注解
1.12013.05JSR 349Java EE 7新增方法级验证(参数、返回值)
2.02017.08JSR 380Java EE 8新增9个注解达到22个。支持容器元素验证
3.02020.07Jakarta管理Jakarta EE 9同上

截止到2.0/3.0版本,共计13 + 9 = 22个内建标准的注解:

序号注解支持类型含义null值是否校验
01@AssertFalsebool元素必须是false
02@AssertTruebool元素必须是true
03@DecimalMaxNumber的子类型(浮点数除外)以及String元素必须是一个数字,且值必须<=最大值
04@DecimalMin同上元素必须是一个数字,且值必须>=最大值
05@Max同上同上
06@Min同上同上
07@Digits同上元素构成是否合法(整数部分和小数部分)
08@Future时间类型(包括JSR310)元素必须为一个将来(不包含相等)的日期(比较精确到毫秒)
09@Past同上元素必须为一个过去(不包含相等)的日期(比较精确到毫秒)
10@NotNullany元素不能为null
11@Nullany元素必须为null
12@Pattern字符串元素需符合指定的正则表达式
13@SizeString/Collection/Map/Array元素大小需在指定范围中
----2.0版本新增了9个注解,如下----
14@Email字符串元素必须为电子邮箱地址
15@NotEmpty容器类型集合的Size必须大于0
16@NotBlank字符串字符串必须包含至少一个非空白的字符
17@Positive数字类型元素必须为正数(不包括0)
18@PositiveOrZero同上同上(包括0)
19@Negative同上元素必须为负数(不包括0)
20@NegativeOrZero同上同上(包括0)
21@PastOrPresent时间类型在@Past基础上包括相等
22@FutureOrPresent时间类型在@Futrue基础上包括相等

值得注意的是,还有些比较常用的注解如@DurationMin、@DurationMax、@Length、@ScriptAssert、@ParameterScriptAssert、@Range、@UniqueElements等,它们不属于标准注解,而属于Hibernate的。但正如上面所说,Hibernate Validator它几乎就是标准,所以在开发中使用也是没有任何问题的。

生存现状

Spring在数据验证这块的API设计得比较失败,Bean Validation很好的弥补了其不足。

虽然Bean Validation存在时间已经很长了,但很多程序员对其依旧“无感”。随着DDD领域驱动模型的流行和普及,它的重要性日趋凸显,毕竟它的设计思想和域模型是一致的,能起到很好的“化学反应”,从而事半功倍。

简单的讲,若你是一个模块化设计爱好者、优雅代码的拥护者、声明式编程的追求者,那么Bean Validation对你的帮助绝非一点点。

实现(框架)

虽说BV规范的实现框架一般有两种:Hibernate Validator和Apache BVal,但实际上基本可认为前者是唯一实现,它就等同于标准,版本对应关系如下:

BV版本HV实现版本
1.04.x
1.15.x
2.06.x
3.07.x

说明:Hibernate Validator 7.x专为Jakarta Bean Validation 3.0打造,适配jakarta.*命名空间

代码示例

导入BV的实现框架:

 
 
 
 
  1.  
  2.     org.hibernate.validator 
  3.     hibernate-validator 
  4.  

准备一个Java Bean,并通过注解声明规则:

 
 
 
 
  1. /** 
  2.  * 在此处添加备注信息 
  3.  * 
  4.  * @author YourBatman. Send email to me 
  5.  * @site https://yourbatman.cn 
  6.  * @date 2021/10/6 10:16 
  7.  * @since 0.0.1 
  8.  */ 
  9. @Data 
  10. public class Person { 
  11.  
  12.     @Positive 
  13.     private long id; 
  14.     @NotBlank 
  15.     private String name; 
  16.     @NotNull 
  17.     @PositiveOrZero 
  18.     private Integer age; 
  19.  

书写验证代码:

 
 
 
 
  1. @Test 
  2. public void validBean() { 
  3.     // 使用默认配置获得验证器 
  4.     Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); 
  5.  
  6.     // 准备(待校验的)Bean 
  7.     Person person = new Person(); 
  8.     person.setId(-1); 
  9.     person.setName("YourBatman"); 
  10.     person.setAge(18); 
  11.  
  12.     // 使用校验器对其执行校验 
  13.     Set> violations = validator.validate(person); 
  14.     // 打印校验结果 
  15.     if (violations.isEmpty()) { 
  16.         System.out.println("校验通过!"); 
  17.     } else { 
  18.         System.out.println("校验不通过!错误详情如下:"); 
  19.         violations.forEach(v -> System.out.println("\t" + v.getPropertyPath() + v.getMessage() + ",但你的值是:" + v.getInvalidValue())); 
  20.     } 

运行程序,输出为:

 
 
 
 
  1. 校验不通过!错误详情如下: 
  2.  id必须是正数,但你的值是:-1 

若运行时碰到这个类找不着:

不要“害怕”,这不正是上篇文章提到内容吗:EL表达式用于数据校验,加入进来即可。

 
 
 
 
  1.  
  2.     org.glassfish 
  3.     jakarta.el 
  4.  

为了加强理解,再来一个方法级的校验示例(参数、返回值):

 
 
 
 
  1. public @Positive int toInt(@NotNull String numStr) { 
  2.     int result = Integer.parseInt(numStr); 
  3.     return result; 

不同于Java Bean表示状态(静态的),方法/构造器是执行期(动态的)才能进行校验,所以执行校验的代码可以这么做:

 
 
 
 
  1. public @Positive int toInt(@NotNull String numStr) throws NoSuchMethodException { 
  2.     // 使用默认配置获得验证器(用于方法、构造器的校验器) 
  3.     ExecutableValidator executableValidator = Validation.buildDefaultValidatorFactory().getValidator().forExecutables(); 
  4.     // 执行参数校验逻辑start... 
  5.     Method currMethod = BeanValidationDemo.class.getMethod("toInt", String.class); 
  6.     Set> violations = executableValidator.validateParameters(this, currMethod, new Object[]{numStr}); 
  7.     // 打印校验结果 
  8.     if (violations.isEmpty()) { 
  9.         System.out.println("校验通过!"); 
  10.     } else { 
  11.         System.out.println("校验不通过!错误详情如下:"); 
  12.         violations.forEach(v -> System.out.println("\t" + v.getPropertyPath() + v.getMessage() + ",但你的值是:" + v.getInvalidValue())); 
  13.         throw new IllegalArgumentException("校验不通过!"); //抛出异常,终止此方法 
  14.     } 
  15.     // 执行参数校验逻辑end... 
  16.  
  17.     int result = Integer.parseInt(numStr); 
  18.     // 执行返回值校验逻辑start 
  19.     // executableValidator.validateReturnValue(); 
  20.     // 执行返回值校验逻辑end 
  21.     return result; 

这样每次方法运行时就能触发校验逻辑了。也许,你会觉得这么做不算完美:侵入性太强了。

是的,不够优雅。但有经验的小伙伴似乎一眼就能看出来如何优化:

没错,就是用AOP来改善坏味道的代码,让校验逻辑和业务逻辑完全分离。

至于如何使用AOP,额,笔者就不用再贴代码示例了吧,给你点提示:

在非Spring场景下,可基于Java EE的@Inteceptors实现

在Spring场景下,你熟悉的场景

本专栏源代码:https://github.com/yourbatman/FXP-java-ee

JPA

Java Persistence API:通过注解或者XML描述【对象-关系表】之间的映射关系,并将实体对象持久化到数据库中。JPA规范给开发者带来了福音:开发者面向JPA规范的接口,但底层的JPA实现可以任意切换:觉得Hibernate好的,可以选择Hibernate JPA实现;觉得TopLink好的,可以选择TopLink JPA实现……这样开发者可以避免为使用Hibernate学习一套ORM框架,为使用TopLink又要再学习一套ORM框架。

如图亦可见,JPA是Java EE的野心:Sun公司希望通过JPA整合ORM技术,实现天下归一。实际情况是,它做到了,除了天朝钟爱更轻量级的MyBatis外,海外依旧是使用JPA居多。

学习过Hibernate的应当可以很轻易的上手Java persistence,因为Java persistence的开发者其实就是原hibernate的开发者。再配合以annotation,可以很轻易的开发出Entity Bean。

注意:是先有Hibernate、TopLink等ORM框架,后才有的JPA来统一天下的

 
 
 
 
  1.  
  2.  
  3.     javax.persistence 
  4.     javax.persistence-api 
  5.     2.2 
  6.  
  7.  
  8.  
  9.  
  10.     jakarta.persistence 
  11.     jakarta.persistence-api 
  12.     3.0.0 
  13.      
  14.  

版本历程

版本发布日期JSR版本对应Java EE版本
1.02006.05--Java EE 5
2.02009.12JSR 317Java EE 6
2.22017.08JSR 338Java EE 8
3.02020.11Jakarta管理Jakarta EE 9

JPA 2.1已经是一个非常成熟的规范,提供了现代应用程序所需的大部分功能。2017年夏天发布的2.2版本(规范内容和2.1规范差不多),新增了对Java 8更好的支持,如它的日期时间Date/Time、重复注解@Repeatable、Stream形式等等,大大增加了其易用性。

下面列出JPA最常用的一些注解:

序号注解标注在哪释义
01@Entity标识实体类是JPA实体,告诉JPA在程序运行时生成实体类对应表
02@Table定义entity主表的name,catalog,schema等属性。也就是说ORM规则自定义
03@Id属性标注此属性为主键
04@GeneratedValue属性JPA通用主键策略生成器,此方式依赖具体数据库(和@Id联用)
05@Column属性定义了映射到数据库的列的所有属性:列名,是否唯一,是否允许为空,是否允许更新等
06@Transient属性该属性并不是一个到数据库表的字段的映射,指定的这些属性不会被持久化,ORM框架将忽略该属性
07@Temporal属性当为java.util中的日期/时间类型时,通过它来指定格式
08@Enumerated属性标注枚举类型如何存库
09@TableGenerator类/属性定义一个主键值生成器,在Id这个元数据的generate=TABLE时,generator属性中可以使用生成器的名字
10@SequenceGenerator类/属性定义一个主键值生成器,在Id这个元数据的generator属性中可以使用生成器的名字
11@SecondaryTable一个entity class可以映射到多表,SecondaryTable用来定义单个从表的名字,主键名字等属性
12@UniqueConstraint注解元数据定义在Table或SecondaryTable元数据里,用来指定建表时需要建唯一约束的列
13@OneToOne一对一的关联。可配置抓取策略、级联操作等
14@ManyToOne多对一的映射,该注解标注的属性通常是数据库表的外键
15@OneToMany一对多的关联,该属性应该为集体类型,在数据库中并没有实际字段
16@ManyToMany多对多的关联.多对多关联上是两个一对多关联,但是在ManyToMany描述中,中间表是由ORM框架自动处理
17@JoinColumn属性如果在entity class的field上定义了关系(one2one或one2many等),通过JoinColumn来定义关系的属性
18@IdClass当entity class使用复合主键时,需要定义一个类作为id class
19@MapKey属性在一对多,多对多关系中,我们可以用Map来保存集合对象。默认用主键值做key,如果使用复合主键,则用id class的实例做key,如果指定了name属性,就用指定的field的值做key
20@OrderBy属性在一对多,多对多关系中,有时希望从数据库加载出来的集合对象是按一定方式排序的,这可以通过OrderBy来实现,默认是按对象的主键升序排列
21@Version属性实体类在乐观事务中的version属性
22@Lob属性指定一个属性作为数据库支持的大对象类型在数据库中存储。使用LobType这个枚举来定义Lob是二进制类型还是字符类型
23@DiscriminatorColumn属性定义在使用SINGLE_TABLE或JOINED继承策略的表中区别不继承层次的列

@Entity和@Table有何区别?答:@Entity表示这个class是实体类,并且使用 默认的 orm规则,即class名即数据库表中表名,class字段名即表中的字段名。若想自定义规则,就要使用@Table来改变包括表名、schema等,且辅助@Column来改变class中字段名与db中表的字段名的映射规则。很明显,@Table + @Column组合方式更灵活和更常用。

典型示例:

 
 
 
 
  1. @Entity  //声明该类是和数据库表映射的实体类 
  2. @Table(name="t_user")  //建立实体类与表的映射关系 
  3. public class User implements Serializable { 
  4.  
  5.  @Id  //声明当前私有属性为主键 
  6.  @GeneratedValue(strategy=GenerationType.IDENTITY)  //配置主键的生成策略,为自增主键 
  7.  @Column(name = "user_id") 
  8.  private Long userId; 
  9.  @Column(name="user_name")   
  10.  private String userName; 
  11.  @Column(name="user_source") 
  12.  private String userSource; 
  13.   

生存现状

国内不乐观,海外坚挺。

实现(框架)

虽说ORM框架较多,但Hibernate的市占率独步天下,几乎能等同于JPA规范。毕竟制定JPA规范的人之前在Hibernate上班呢~

Hibernate 从3.2开始,开始兼容JPA。

版本日期JPA版本info
5.02015.082.1兼容到JDK 6,提供hibernate-java8兼容到JDK 8
5.12016.022.1小版本迭代逐步放弃6、7,最低要求8
5.22016.062.1最低要求JDK 8
5.32018.052.2最低要求JDK 8,全面使用maven管理器所有的模块artifacts
5.42018.122.2EntityGraph增强。最低要求8,支持11和17
5.52021.062.2/3.0同上。额外通过hibernate-core-jakarta增加了对JPA 3.0规范的支持
5.6开发中2.2/3.0开发中
6.0开发中2.2/3.0开发中

值得注意的是,Hibernate采用模块化管理,其中最重要的当属hibernate-core,还有hibernate-tools、hibernate-jcache、hibernate-hikaricp、hibernate-ehcache等等

代码示例

在classpath下准备一个hibernate.cfg.xml标准文件(亦可不使用xml文件,完全采用编程方式设置configuration):

 
 
 
 
  1.  
  2.         "-//Hibernate/Hibernate Configuration DTD 3.0//EN" 
  3.         "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> 
  4.  
  5.  
  6.      
  7.      
  8.         com.mysql.cj.jdbc.Driver 
  9.         jdbc:mysql://localhost:3306/demo 
  10.         root 
  11.         root 
  12.         org.hibernate.dialect.MySQLDialect 
  13.          
  14.         com.zaxxer.hikari.hibernate.HikariConnectionProvider 
  15.  
  16.          
  17.         true 
  18.         true 
  19.  
  20.          
  21.         validate 
  22.      
  23.  

准备一个entity(并在数据库创建好表结构):

 
 
 
 
  1. /** 
  2.  * 在此处添加备注信息 
  3.  * 
  4.  * @author YourBatman. Send email to me 
  5.  * @site https://yourbatman.cn 
  6.  * @date 2021/10/6 16:11 
  7.  * @since 0.0.1 
  8.  */ 
  9. @Data 
  10. @Entity 
  11. @Table(name = "user") 
  12. public class User { 
  13.  
  14.     @Id 
  15.     @GeneratedValue(strategy = GenerationType.IDENTITY) 
  16.     private Long id; 
  17.  
  18.     @Column(name = "name") 
  19.     private String name; 
  20.     @Column(name = "age") 
  21.     private Integer age; 
  22.  

启动Hibernate:

 
 
 
 
  1. /** 
  2.  * 在此处添加备注信息 
  3.  * 
  4.  * @author YourBatman. Send email to me 
  5.  * @site https://yourbatman.cn 
  6.  * @date 2021/10/6 16:06 
  7.  * @since 0.0.1 
  8.  */ 
  9. public class JPADemo { 
  10.  
  11.     @Test 
  12.     public void fun1() { 
  13.         // 准备Hibernate的Session 
  14.         Configuration configure = new Configuration().configure(); 
  15.         SessionFactory sessionFactory = configure.buildSessionFactory(); 
  16.         Session session = sessionFactory.openSession(); 
  17.         Transaction transaction = session.beginTransaction(); 
  18.  
  19.         // 业务逻辑start 
  20.         User user = new User(); 
  21.         user.setName("YourBatman"); 
  22.         user.setAge(10); 
  23.         session.save(user); 
  24.         System.out.println("保存成功,id为:" + user.getId()); 
  25.         // 业务逻辑end 
  26.  
  27.         transaction.commit(); 
  28.         session.close(); 
  29.         sessionFactory.close(); 
  30.     } 
  31.  

大功告成。

在国内,即使使用JPA,大都是使用Spring Data JPA,可大大简化开发。当然喽,企业级项目使用MyBatis还是居多~

工程源代码:https://github.com/yourbatman/FXP-java-ee

总结

本文拉齐了Java EE的两项热门技术:Bean Validation和JPA,它俩的实现恰巧都是Hibernate,所以放在本篇一起毫无违和感。

文章标题:【方向盘】版本历史&代码示例之:Bean Validation、JPA
链接地址:http://www.shufengxianlan.com/qtweb/news1/36401.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联