本文转载自微信公众号「Java方向盘」,作者方向盘 。转载本文请联系Java方向盘公众号。
10年积累的网站设计、成都网站制作经验,可以快速应对客户对网站的新想法和需求。提供各种问题对应的解决方案。让选择我们的客户得到更好、更有力的网络服务。我虽然不认识你,你也不认识我。但先网站制作后付款的网站建设流程,更有东风免费网站建设让你可以放心的选择与我们合作。
数据校验:在任何时候,当你想要处理一个应用程序的逻辑时,确保数据的正确性是你必须要考虑和面对的事情。也就说我们必须通过某种手段,确保输入进来的数据是正确的。
然而,应用程序一般是分层的,同样的验证逻辑往往会出现在不同的层,这样就会给代码组织管理上带来冗余负担。为了避免这类情况的发生,最好就是做一层抽象:将验证逻辑与响应的模型进行绑定,这就是Bean Validation。
Bean Validation简直就是业务开发中祛掉坏味道代码的利器,完美的实现契约式编程,大大提高开发效率,降低出错概率。
注意:Bean Validation它是一种通用规范,并不只属于Web层技术,即使大概率你可能只在Spring MVC中使用过它~
javax.validation validation-api 2.0.1.Final jakarta.validation jakarta.validation-api 3.0.0
版本 | 发布日期 | JSR版本 | 对应Java EE版本 | 主要特性 |
---|---|---|---|---|
1.0 | 2009.11 | JSR 303 | Java EE 6 | 对JavaBean进行验证,提供13个注解 |
1.1 | 2013.05 | JSR 349 | Java EE 7 | 新增方法级验证(参数、返回值) |
2.0 | 2017.08 | JSR 380 | Java EE 8 | 新增9个注解达到22个。支持容器元素验证 |
3.0 | 2020.07 | Jakarta管理 | Jakarta EE 9 | 同上 |
截止到2.0/3.0版本,共计13 + 9 = 22个内建标准的注解:
序号 | 注解 | 支持类型 | 含义 | null值是否校验 |
---|---|---|---|---|
01 | @AssertFalse | bool | 元素必须是false | 否 |
02 | @AssertTrue | bool | 元素必须是true | 否 |
03 | @DecimalMax | Number的子类型(浮点数除外)以及String | 元素必须是一个数字,且值必须<=最大值 | 否 |
04 | @DecimalMin | 同上 | 元素必须是一个数字,且值必须>=最大值 | 否 |
05 | @Max | 同上 | 同上 | 否 |
06 | @Min | 同上 | 同上 | 否 |
07 | @Digits | 同上 | 元素构成是否合法(整数部分和小数部分) | 否 |
08 | @Future | 时间类型(包括JSR310) | 元素必须为一个将来(不包含相等)的日期(比较精确到毫秒) | 否 |
09 | @Past | 同上 | 元素必须为一个过去(不包含相等)的日期(比较精确到毫秒) | 否 |
10 | @NotNull | any | 元素不能为null | 是 |
11 | @Null | any | 元素必须为null | 是 |
12 | @Pattern | 字符串 | 元素需符合指定的正则表达式 | 否 |
13 | @Size | String/Collection/Map/Array | 元素大小需在指定范围中 | 否 |
-- | -- | 2.0版本新增了9个注解,如下 | -- | -- |
14 | 字符串 | 元素必须为电子邮箱地址 | 否 | |
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.0 | 4.x |
1.1 | 5.x |
2.0 | 6.x |
3.0 | 7.x |
说明:Hibernate Validator 7.x专为Jakarta Bean Validation 3.0打造,适配jakarta.*命名空间
导入BV的实现框架:
org.hibernate.validator hibernate-validator
准备一个Java Bean,并通过注解声明规则:
- /**
- * 在此处添加备注信息
- *
- * @author YourBatman. Send email to me
- * @site https://yourbatman.cn
- * @date 2021/10/6 10:16
- * @since 0.0.1
- */
- @Data
- public class Person {
- @Positive
- private long id;
- @NotBlank
- private String name;
- @NotNull
- @PositiveOrZero
- private Integer age;
- }
书写验证代码:
- @Test
- public void validBean() {
- // 使用默认配置获得验证器
- Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
- // 准备(待校验的)Bean
- Person person = new Person();
- person.setId(-1);
- person.setName("YourBatman");
- person.setAge(18);
- // 使用校验器对其执行校验
- Set
> violations = validator.validate(person); - // 打印校验结果
- if (violations.isEmpty()) {
- System.out.println("校验通过!");
- } else {
- System.out.println("校验不通过!错误详情如下:");
- violations.forEach(v -> System.out.println("\t" + v.getPropertyPath() + v.getMessage() + ",但你的值是:" + v.getInvalidValue()));
- }
- }
运行程序,输出为:
- 校验不通过!错误详情如下:
- id必须是正数,但你的值是:-1
若运行时碰到这个类找不着:
不要“害怕”,这不正是上篇文章提到内容吗:EL表达式用于数据校验,加入进来即可。
org.glassfish jakarta.el
为了加强理解,再来一个方法级的校验示例(参数、返回值):
- public @Positive int toInt(@NotNull String numStr) {
- int result = Integer.parseInt(numStr);
- return result;
- }
不同于Java Bean表示状态(静态的),方法/构造器是执行期(动态的)才能进行校验,所以执行校验的代码可以这么做:
- public @Positive int toInt(@NotNull String numStr) throws NoSuchMethodException {
- // 使用默认配置获得验证器(用于方法、构造器的校验器)
- ExecutableValidator executableValidator = Validation.buildDefaultValidatorFactory().getValidator().forExecutables();
- // 执行参数校验逻辑start...
- Method currMethod = BeanValidationDemo.class.getMethod("toInt", String.class);
- Set
> violations = executableValidator.validateParameters(this, currMethod, new Object[]{numStr}); - // 打印校验结果
- if (violations.isEmpty()) {
- System.out.println("校验通过!");
- } else {
- System.out.println("校验不通过!错误详情如下:");
- violations.forEach(v -> System.out.println("\t" + v.getPropertyPath() + v.getMessage() + ",但你的值是:" + v.getInvalidValue()));
- throw new IllegalArgumentException("校验不通过!"); //抛出异常,终止此方法
- }
- // 执行参数校验逻辑end...
- int result = Integer.parseInt(numStr);
- // 执行返回值校验逻辑start
- // executableValidator.validateReturnValue();
- // 执行返回值校验逻辑end
- return result;
- }
这样每次方法运行时就能触发校验逻辑了。也许,你会觉得这么做不算完美:侵入性太强了。
是的,不够优雅。但有经验的小伙伴似乎一眼就能看出来如何优化:
没错,就是用AOP来改善坏味道的代码,让校验逻辑和业务逻辑完全分离。
至于如何使用AOP,额,笔者就不用再贴代码示例了吧,给你点提示:
在非Spring场景下,可基于Java EE的@Inteceptors实现
在Spring场景下,你熟悉的场景
本专栏源代码:https://github.com/yourbatman/FXP-java-ee
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来统一天下的
javax.persistence javax.persistence-api 2.2 jakarta.persistence jakarta.persistence-api 3.0.0
版本 | 发布日期 | JSR版本 | 对应Java EE版本 |
---|---|---|---|
1.0 | 2006.05 | -- | Java EE 5 |
2.0 | 2009.12 | JSR 317 | Java EE 6 |
2.2 | 2017.08 | JSR 338 | Java EE 8 |
3.0 | 2020.11 | Jakarta管理 | 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组合方式更灵活和更常用。
典型示例:
- @Entity //声明该类是和数据库表映射的实体类
- @Table(name="t_user") //建立实体类与表的映射关系
- public class User implements Serializable {
- @Id //声明当前私有属性为主键
- @GeneratedValue(strategy=GenerationType.IDENTITY) //配置主键的生成策略,为自增主键
- @Column(name = "user_id")
- private Long userId;
- @Column(name="user_name")
- private String userName;
- @Column(name="user_source")
- private String userSource;
- }
国内不乐观,海外坚挺。
虽说ORM框架较多,但Hibernate的市占率独步天下,几乎能等同于JPA规范。毕竟制定JPA规范的人之前在Hibernate上班呢~
Hibernate 从3.2开始,开始兼容JPA。
版本 | 日期 | JPA版本 | info |
---|---|---|---|
5.0 | 2015.08 | 2.1 | 兼容到JDK 6,提供hibernate-java8兼容到JDK 8 |
5.1 | 2016.02 | 2.1 | 小版本迭代逐步放弃6、7,最低要求8 |
5.2 | 2016.06 | 2.1 | 最低要求JDK 8 |
5.3 | 2018.05 | 2.2 | 最低要求JDK 8,全面使用maven管理器所有的模块artifacts |
5.4 | 2018.12 | 2.2 | EntityGraph增强。最低要求8,支持11和17 |
5.5 | 2021.06 | 2.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):
- "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
- "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
com.mysql.cj.jdbc.Driver jdbc:mysql://localhost:3306/demo root root org.hibernate.dialect.MySQLDialect com.zaxxer.hikari.hibernate.HikariConnectionProvider true true validate
准备一个entity(并在数据库创建好表结构):
- /**
- * 在此处添加备注信息
- *
- * @author YourBatman. Send email to me
- * @site https://yourbatman.cn
- * @date 2021/10/6 16:11
- * @since 0.0.1
- */
- @Data
- @Entity
- @Table(name = "user")
- public class User {
- @Id
- @GeneratedValue(strategy = GenerationType.IDENTITY)
- private Long id;
- @Column(name = "name")
- private String name;
- @Column(name = "age")
- private Integer age;
- }
启动Hibernate:
- /**
- * 在此处添加备注信息
- *
- * @author YourBatman. Send email to me
- * @site https://yourbatman.cn
- * @date 2021/10/6 16:06
- * @since 0.0.1
- */
- public class JPADemo {
- @Test
- public void fun1() {
- // 准备Hibernate的Session
- Configuration configure = new Configuration().configure();
- SessionFactory sessionFactory = configure.buildSessionFactory();
- Session session = sessionFactory.openSession();
- Transaction transaction = session.beginTransaction();
- // 业务逻辑start
- User user = new User();
- user.setName("YourBatman");
- user.setAge(10);
- session.save(user);
- System.out.println("保存成功,id为:" + user.getId());
- // 业务逻辑end
- transaction.commit();
- session.close();
- sessionFactory.close();
- }
- }
大功告成。
在国内,即使使用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。内容未经允许不得转载,或转载时需注明来源: 创新互联