Hibernate学习笔记2
1. 摘要
本篇主要介绍以下几个问题的处理:
- 枚举类型的映射
- 时间类型的映射
- JPA中的自定义属性映射
- 自动生成参数(如自动生成数据的插入时间,自动更新更新时间)
2. Domain Model
1.1. Basic Type
上一篇的Basic Type小节中学习了如何自定义Basic Type,这一节继续学习一些类型映射的方式。
1.1.1. 枚举类型的映射
首先介绍枚举类型的映射。只需要在枚举类型的变量上加上@Enumerated
注解,该注解有一个参数,表示将该枚举的什么信息保存到数据库中;值有两个可选:
- ORDINAL:将枚举在定义时的顺序索引作为存储值。
- STRING:将枚举的名字作为存储值。
举个栗子,我们为之前的User
添加一个枚举类型的性别属性,该枚举类型定义如下:
public enum Gender {
FEMALE, MALE
}
接着,为User
添加gender属性,我们希望直接将枚举的字面值保存到数据库中,因此这么做:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@Type(type = "String[]")
private String[] hobbies;
@Enumerated(EnumType.STRING)
private Gender gender;
...
}
大家还可以试一试修改注解参数EnumType.STRING
为EnumType.ORDINAL
,测试方法就不给出了,以上便是搞定枚举方式。
1.1.2. LOBs类型的映射
由于本人很少进二进制数据直接存入数据库中,因此没有学习这种方式。想要学习的可看官方文档。
1.1.3. 时间类型映射
在标准的SQL中定义了三种时间类型:
- DATE:表示日期(年月日)
- TIME:表示时间(时分秒)
- TIMESTAMP:表示日期与时间并且包含纳秒。
在Java中,我们通常使用java.util.Date
和java.util.Calendar
表示时间。另外在Java8中又定义了丰富的和时间相关的类,在java.time
包下。
和枚举类型一样,我们在映射时只需要一个@Temporal
注解便可以搞定,它有一个参数,指定映射的sql时间类型,取值就是上面提到的3种。
1.1.4. JPA 2.1 AttributeConverters
在上一篇博客中,我们已经学习了如何通过自定义Basic Type来处理Hibernate不支持的类型映射。事实上 JPA 2.1定义了AttributeConverters
接口来做这件事,并且Hibernate也支持。下面举个栗子来演示其用法,还是之前那个将String[]
以json字符串的形式保存到数据库中。
定义一个转换类StringArrayConverter
,需要实现AttributeConverters
接口
@Converter
public class StringArrayConverter implements AttributeConverter<String[], String> {
@Override
public String convertToDatabaseColumn(String[] attribute) {
return JSON.toJSONString(attribute);
}
@Override
public String[] convertToEntityAttribute(String dbData) {
JSONArray jsonArray = JSON.parseArray(dbData);
String[] result = new String[jsonArray.size()];
for (int i = 0; i < result.length; i++) {
result[i] = jsonArray.getString(i);
}
return result;
}
}
然后在User
的hobbies
属性上使用@Convert
注解:
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@Convert(converter = StringArrayConverter.class)
private String[] hobbies;
@Enumerated(EnumType.STRING)
private Gender gender;
...
}
PS: 经测试,以上做法是OK的,但是使用Idea时,报了一个错:"Basic attribute type should be "String[]" "
,不影响运行,但是挺烦人的,知道原因的童鞋请告诉我。
1.1.5. Generated properties
有时候我们想让某些字段自动的生成,而不用每次去手动指定,这时候就可以使用Generated properties特性。
举个栗子,比如我们有时候需要保存数据库中每条记录的创建和最新修改的时间,可以这么做:
首先是定义一个生成器,告诉Hibernate如何去自动生成数据,这需要实现ValueGenerator<T>
接口:
public class LoggedDateGenerator implements ValueGenerator<Date> {
@Override
public Date generateValue(Session session, Object owner) {
return new Date();
}
}
以上需要实现generateValue
方法,该方法的返回值便是要生成的值,这里我们只是简单的返回当前的日期(实际上最好要判断一下updated是否为空,若为空,说明是第一次创建,应该将created赋给updated(前提是created不为空),不然可能会导致数据插入是created的时间与updated的时间不一致)。两个参数一个是我们熟悉的Session
,还一个是Session
在进行insert和uodate操作时操纵的对象。
之后便可以使用了,还是已User为例,为其加上created
与updated
属性:
@GeneratorType(type = LoggedDateGenerator.class, when = GenerationTime.INSERT)
@Temporal(TemporalType.TIMESTAMP)
private Date created;
@GeneratorType(type = LoggedDateGenerator.class, when = GenerationTime.ALWAYS)
@Temporal(TemporalType.TIMESTAMP)
private Date updated;
我们通过@GeneratorType
为属性加上生成器,并指定生成的时机:
- GenerationTiming.NEVER:表示不使用生成器,是默认值。
- GenerationTiming.INSERT:表示只在插入时使用生成器生成数据。
- GenerationTiming.ALWAYS:表示始终使用生成器生成数据。
@Temporal
上面有介绍过,是用来映射时间类型的。
事实上,Hibernate考虑到了以上操作的普遍性,因此提供了直接的注解支持,只需要将以上注解改为这样即可:
@CreationTimestamp
private Date created;
@UpdateTimestamp
private Date updated;
我们还可以使用Generation来完成以上工作,注意和上面Generator的区别,事实上Generation依赖于Generator(不是必须),并且比Generator更加的灵活。下面给出Generation实现的方式:
首先要定义一个注解类,比如叫CreationTimestamp
,我们需要的是被这个注解标记了的属性会在插入时自动生成时间戳,并且这个时间戳数据是由数据库生成的:
`@Retention(RetentionPolicy.RUNTIME) @ValueGenerationType(generatedBy = CreationValueGeneration.class) public @interface CreationTimestamp { }
@Retention
注解不用说,是来定义注解保存到什么时候。@ValueGenerationType
注解用来指定Generation。
下面是Generation的实现:
public class CreationValueGeneration implements AnnotationValueGeneration<CreationTimestamp> {
@Override
public void initialize(CreationTimestamp annotation, Class<?> propertyType) {
}
/**
* 返回值决定了生成数据的时机,和之前的一样
*/
public GenerationTiming getGenerationTiming() {
return GenerationTiming.INSERT;
}
/**
* 返回一个Generator。若想让数据库来生成值,也可以返回null
*/
public ValueGenerator<?> getValueGenerator() {
return null;
}
/**
* 返回值表示列是否被包含在sql中
*/
public boolean referenceColumnInSql() {
return true;
}
/**
* 返回值表示在sql语句中,列使用的值
*/
public String getDatabaseGeneratedReferencedColumnValue() {
return "current_timestamp";
}
}
这样就可以使用了:
@CreationTimestamp
private Date created;
若还是想和之前一样,让Hibernate来生成时间戳,可以修改Generation实现类的方法:
public GenerationTiming getGenerationTiming() {
return GenerationTiming.INSERT;
}
public ValueGenerator<?> getValueGenerator() {
return (session, owner) -> new Date( );
}
public boolean referenceColumnInSql() {
return false;
}
public String getDatabaseGeneratedReferencedColumnValue() {
return null;
}