如何实现属性转换器以支持自定义类型(附示例)
支持的转换和限制
AttributeConverter的总体概念很简单。AttributeConverter 接口的2个方法定义了2个转换。一个是将用于实体属性的类型转换为JDBC驱动在数据库中插入或更新记录时处理的类型。另一个是将JDBC驱动从数据库中读取记录时返回的类型转换为实体属性的类型。
基于这个简单的概念,属性转换器的能力和限制变得很明显。
你可以在所有映射到你的表模型中的1列的基本属性上使用它,这些属性由实体类、映射的超类或可嵌入类定义。
但是转换器不能处理更复杂的类型,比如整个*ElementCollection、to-many关联,或者任何你想映射到多个数据库列的属性。你也不能在主键属性或版本属性上使用AttributeConverter* 。JPA规范为这些属性定义了一个特定的处理方法,这可能会导致冲突。而那些被*@Temporal或@Enumerated注释的属性也不被支持。这是因为这些注释已经定义了对数据库列的映射。你需要决定是使用AttributeConverter*还是其他类型的映射,并且只添加相应的注释。
你不能使用AttributeConverter的情况列表可能看起来比你可以使用它的情况要长得多。但是不要担心,AttributeConverter 非常有用,它可以处理几乎所有的标准用例。
实现一个AttributeConverter
让我们实现一个AttributeConverter ,在java.awt.Color类型的实体属性和包含6位数十六进制值的字符串 之间进行转换。
实现AttributeConverter需要一个实现javax.persistence.AttributeConverter(JPA 1 & 2)或jakarta.persistence.AttributeConverter(JPA 3)接口的类。除了包的名字,这两个接口是相同的。正如你在代码片段中看到的,AttributeConverter接口使用了泛型。这些是实体属性的类型和由JDBC驱动处理的类型。在这个例子中,属性将是Color 类型,JDBC驱动将处理一个String:
@Converter(autoApply = true)
public class ColorConverter implements AttributeConverter<Color, String> {
Logger log = LogManager.getLogger(this.getClass().getName());
@Override
public String convertToDatabaseColumn(Color attribute) {
String hex = "#"+Integer.toHexString(attribute.getRGB()).substring(0,6);
log.info("Convert "+attribute+" to "+hex);
return hex;
}
@Override
public Color convertToEntityAttribute(String dbData) {
Color color = Color.decode(dbData);
log.info("Convert "+dbData+" to "+color);
return color;
}
}
你还需要用JPA的*@Converter注解来注释你的转换器类。@Converter注解告诉你的持久化提供者,比如说Hibernate,这是一个属性转换器。如果你想对所有颜色类型的实体属性使用这个转换器,你可以把它的autoApply* 属性设置为true。如果你不想这么做,请查看下面的章节,在那里我将向你展示如何为一个特定的属性激活转换器。
AttributeConverter 的实现是非常简单的。该接口定义了方法convertToDatabaseColumn 和convertToEntityAttribute。Hibernate和任何其他JPA实现都会调用这些方法,将实体属性的值转换为JDBC驱动处理的类型,反之亦然。
激活一个AttributeConverter
你可以通过两种方式激活AttributeConverter:
- 最简单的是将*@Converter注解的autoApply*属性设置为true。然后你的持久化提供者将对所有给定类型的实体属性使用该转换器。
- 或者你可以用javax.persistence.Convert(JPA 1 & 2)或jakarta.persistence.Convert(JPA 3)注解来注解一个实体属性,并引用你的AttributeConverter实现。然后你的持久化提供者只使用该属性的转换器。
下面的代码片断显示了这种方法的一个例子:
@Entity
public class Rectangle {
@Id
@GeneratedValue
private Integer id;
private Integer x;
private Integer y;
@Convert(converter = ColorConverter.class)
private Color color;
...
}
这就是你需要做的,实现一个提供自定义类型映射的AttributeConverter。
该转换器被透明地使用
在你为一个属性激活AttributeConverter后,你的持久化提供者会在所有影响该实体属性的操作中透明地使用该转换器。这包括对该实体类进行的所有读和写操作以及与该属性比较的所有绑定参数。
你可以在下面的例子中看到这一点。它读取了一个颜色为白色的Rectangle 实体对象,并将其颜色改为黑色:
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Rectangle r = em.createQuery("SELECT r FROM Rectangle r WHERE r.color = :color", Rectangle.class)
.setParameter("color", Color.WHITE)
.getSingleResult();
r.setColor(Color.BLACK);
em.getTransaction().commit();
em.close();
我使用Hibernate作为我的JPA实现,用于下面的日志输出,并激活了我推荐的开发系统的日志配置。你可以在日志文件中看到执行的SQL语句和AttributeConverter实现写的信息:
19:11:37,114 INFO [com.thorben.janssen.model.ColorConverter] - Convert java.awt.Color[r=255,g=255,b=255] to #ffffff
19:11:37,170 DEBUG [org.hibernate.SQL] - select r1_0.id,r1_0.color,r1_0.x,r1_0.y from Rectangle r1_0 where r1_0.color=?
19:11:37,171 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter [1] as [VARCHAR] - [#ffffff]
19:11:37,179 INFO [com.thorben.janssen.model.ColorConverter] - Convert #ffffff to java.awt.Color[r=255,g=255,b=255]
19:11:37,181 INFO [com.thorben.janssen.model.ColorConverter] - Convert java.awt.Color[r=255,g=255,b=255] to #ffffff
19:11:37,181 INFO [com.thorben.janssen.model.ColorConverter] - Convert #ffffff to java.awt.Color[r=255,g=255,b=255]
19:11:37,184 DEBUG [org.hibernate.stat.internal.StatisticsImpl] - HHH000117: HQL: SELECT r FROM Rectangle r WHERE r.color = :color, time: 39ms, rows: 1
19:11:37,192 DEBUG [org.hibernate.SQL] - update Rectangle set color=?, x=?, y=? where id=?
19:11:37,193 INFO [com.thorben.janssen.model.ColorConverter] - Convert java.awt.Color[r=0,g=0,b=0] to #ff0000
19:11:37,193 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter [1] as [VARCHAR] - [#ff0000]
19:11:37,193 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter [2] as [INTEGER] - [10]
19:11:37,193 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter [3] as [INTEGER] - [20]
19:11:37,193 TRACE [org.hibernate.orm.jdbc.bind] - binding parameter [4] as [INTEGER] - [1]
19:11:37,196 INFO [com.thorben.janssen.model.ColorConverter] - Convert java.awt.Color[r=0,g=0,b=0] to #ff0000
19:11:37,196 INFO [com.thorben.janssen.model.ColorConverter] - Convert #ff0000 to java.awt.Color[r=255,g=0,b=0]
19:11:37,203 INFO [org.hibernate.engine.internal.StatisticalLoggingSessionEventListener] - Session Metrics {
31200 nanoseconds spent acquiring 1 JDBC connections;
26100 nanoseconds spent releasing 1 JDBC connections;
191100 nanoseconds spent preparing 2 JDBC statements;
4859600 nanoseconds spent executing 2 JDBC statements;
0 nanoseconds spent executing 0 JDBC batches;
0 nanoseconds spent performing 0 L2C puts;
0 nanoseconds spent performing 0 L2C hits;
0 nanoseconds spent performing 0 L2C misses;
13747100 nanoseconds spent executing 1 flushes (flushing a total of 1 entities and 0 collections);
770600 nanoseconds spent executing 1 partial-flushes (flushing a total of 0 entities and 0 collections)
}
结论
AttributeConverter 提供了一种简单和可移植的方式来定义自定义类型映射。你可以把它用于所有你想映射到1个数据库列的基本属性。在这篇文章中,我用它来将一个java.awt.Color类型的实体属性持久化为一个6位数的十六进制代码。但是,这当然不是你能实现的唯一一种映射方式。我在其他文章中用它来改进Hibernate的标准枚举映射,并在不支持 LocalDate和LocalDateTime类型的旧版Hibernate中映射LocalDate和LocalDateTime 。
正如你在这篇文章中看到的,实现AttributeConverter很简单。你只需要实现AttributeConverter 接口及其2个转换方法,并用*@Converter注解来注释该类。如果你将该注解的autoApply* 属性设置为*"true*",你的持久化提供者将对所有支持类型的实体属性使用该转换器。如果你不设置该属性或将其设置为false,你需要用*@Convert*注解每个你想使用转换器的实体属性并引用你的转换器实现。
推荐阅读
-
35 岁实现财务*,腾讯程序员手握2300万提前退休?-1000万房产、1000万腾讯股票、加上300万的现金,一共2300万的财产。有网友算了一笔账,假设1000万的房产用于自住,剩下1300万资产按照平均税后20-50万不等进行计算,大约花上26-60年左右的时间才能赚到这笔钱。也就是说,普通人可能奋斗一辈子,才能赚到这笔钱。在很多人还在为中年危机而惶惶不可终日的时候,有的人的35岁,就已经安全着陆,试问哪个打工人不羡慕?但问题是有这样财富积累必然有像样的实力做靠山。没有人可以不劳而获。 看到这里,肯定有人说,那么对于普通人来说,卷可能真就成了唯一的出路。但是卷也有轻松的卷,“偷懒”的卷法,对于程序员而言,刨除掉一时无法改掉的开会传统占用的大部分时间,如何把有限的时间和精力放在真正重要的架构设计、需求设计上,而不是重复的造*,编码、改bug、手动测试。因此在科技改变生活的今天,学会使用AI工具成为程序员们的必备技能。 以全栈式全自动的软件开发工具飞算SoFlu软件机器人为例,作为全球首款面向微服务架构设计和最佳实践的软件机器人,SoFlu软件机器人改变了原来手工编码的作业模式,通过可视化拖拽方式以及参数配置就能实现等同于编写复杂代码的业务逻辑,在设计业务逻辑时就完成了微服务应用开发,做到“业务即图,图即代码”,实现“软件开发,十倍提效”。 而作为飞算SoFlu软件机器人的一个重要组成部分,FuncGPT(慧函数)支持所有类型函数创建。通过自然语言描述Java函数需求,实时生成高质量、高可读性的Java函数代码。生成代码可直接复制到IDEA,或一键导入Java全自动开发工具函数库。 FuncGPT(慧函数)具有五大核心能力: ● 自然语言:通过自然语言即可生成函数,降低软件开发门槛。 ● 秒级函数开发:全自动秒级开发函数,秒级完成,效率千倍提升。 ● 一站式函数开发应用:函数生成即可直接应用,效率、安全有保障。 ● 质量可靠:生成的函数代码符合业界规范、具备良好的可读性,符合最优解。 ● 灵活生成:可以根据开发人员的具体需求生成并支持调整和修改。 以“将文件流转换为File对象”这个需求为例,以下是FuncGPT(慧函数)秒级生成的代码: / 类名:FileConverter // 函数名:convertStreamToFile // 函数功能:将文件流转换为File对象 // POM依赖包:无 import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; public class FileConverter { /** * 将文件流转换为File对象 * @param inputStream 文件流 * @param filePath 文件路径 * @param fileName 文件名 * @return 转换后的File对象 * @throws IOException 如果发生I/O异常 */ public static File convertStreamToFile(InputStream inputStream, String filePath, String fileName) throws IOException { File file = new File(filePath + File.separator + fileName); // 创建File对象 try (FileOutputStream outputStream = new FileOutputStream(file)) { // 创建文件输出流 byte buffer = new byte[1024]; int bytesRead; while ((bytesRead = inputStream.read(buffer)) != -1) { // 从文件流读取数据并写入文件 outputStream.write(buffer, 0, bytesRead); } } return file; // 返回转换后的File对象 } } // 函数示例 // 将文件流转换为File对象示例 // 入参:inputStream,文件流 // 入参:filePath,文件路径 // 入参:fileName,文件名 // 出参:file,转换后的File对象 // 调用示例: // InputStream inputStream = new FileInputStream("example.txt"); // String filePath = "C:\\Users\\User\\Documents"; // String fileName = "example.txt"; // File file = FileConverter.convertStreamToFile(inputStream, filePath, fileName); // System.out.println(file.getAbsolutePath); // 输出结果:例如,将文件流转换为File对象后,文件的绝对路径为:C:\Users\User\Documents\example.txt // 则输出结果为:C:\Users\User\Documents\example.txt 通过分析,不难发现以上代码:
-
如何实现属性转换器以支持自定义类型(附示例)
-
南邮OJ Web任务大揭秘:层层挑战剖析 1. 挑战一:迷宫般的目录探索 题目作者似乎穷举了所有可能的目录组合,最终在404.php中的