首页 > 代码库 > Hibernate学习笔记

Hibernate学习笔记

1. 摘要

本篇主要介绍以下几个问题的处理:

  1. 枚举类型的映射
  2. 时间类型的映射
  3. JPA中的自定义属性映射
  4. 自动生成参数(如自动生成数据的插入时间,自动更新更新时间)

2. Domain Model

1.1. Basic Type

上一篇的Basic Type小节中学习了如何自定义Basic Type,这一节继续学习一些类型映射的方式。

1.1.1. 枚举类型的映射

首先介绍枚举类型的映射。只需要在枚举类型的变量上加上@Enumerated注解,该注解有一个参数,表示将该枚举的什么信息保存到数据库中;值有两个可选:

  1. ORDINAL:将枚举在定义时的顺序索引作为存储值。
  2. 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.STRINGEnumType.ORDINAL,测试方法就不给出了,以上便是搞定枚举方式。

1.1.2. LOBs类型的映射

由于本人很少进二进制数据直接存入数据库中,因此没有学习这种方式。想要学习的可看官方文档。

1.1.3. 时间类型映射

在标准的SQL中定义了三种时间类型:

  1. DATE:表示日期(年月日)
  2. TIME:表示时间(时分秒)
  3. TIMESTAMP:表示日期与时间并且包含纳秒。

在Java中,我们通常使用java.util.Datejava.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;
    }
}

然后在Userhobbies属性上使用@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为例,为其加上createdupdated属性:

@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为属性加上生成器,并指定生成的时机:

  1. GenerationTiming.NEVER:表示不使用生成器,是默认值。
  2. GenerationTiming.INSERT:表示只在插入时使用生成器生成数据。
  3. 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;
}




1. Architecture(总体结构)

俗话说有图有真相,先看一张图:

上图清楚地描述了Hibernate在一个Java应用中所处的位置:位于Data Access Layer(数据访问层)Relation Database(关系型数据库)之间。最原始地,我们在数据访问层中是通过JDBC去操作数据库的,但JDBC编程起来非常繁琐,并且需要我们手动去处理数据库中每条数据与其对应对象的转换(即所谓的ORM,Object Relational Mapping,对象关系映射),Hibernate很好地解决了以上问题。Hibernate实现了JPA(Java Persistence API,Java官方给出的持久化API),因此我们可以使用JPA去使用Hibernate,当然也可以使用Hibernate自己的(原生的)API。

再看一个图,这是我们常用的Hibernate的接口(类)的继承体系结构图,以后再作介绍,先有个大致的印象:

2. Domain Model(领域模型)

Domain Model(领域模型)是一个抽象的系统,它描述了我们关注的问题所包含的知识、所产生的影响或活动的范围,该模型可以帮助我们解决与该领域相关的问题。

对编程而言,我们通常编写的POJO/JavaBean便是领域模型(或者更具体地叫做anemic domain model,贫血领域模型),这是Hibernate的中心角色。

2.1 快速入门

概念讲了一大堆,我想你更加关心的是这东西到底咋用啊。下面来一个入门例子。例子很简单,我们有一个User(用户)POJO,我们要对其进行CURD(增删改查)操作。User数据结构如下:

public class User {

    private Long id;
    private String name;

    // setter、getter方法略
}

首先创建一个Maven项目(强烈建议使用Maven,Gradle等项目构建工具来开发,不要再到处找jar包导入了),然后在pom.xml中引入Hibernate和一些必要的依赖,如下:

<dependencies>
    <dependency>
        <groupId>org.hibernate</groupId>
         <!--如果使用Hibernate原生API,使用hibernate-core代替hibernate-entitymanager -->
        <artifactId>hibernate-entitymanager</artifactId>
        <version>5.2.8.Final</version>
    </dependency>
    <!--mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>6.0.3</version>
    </dependency>
    <!--测试框架-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>

依赖搞定,在这例子中我们使用JPA,现在配置Hibernate。在resources目录(如果不是Maven项目就在类的根目录)下新建META-INF文件夹,再在META-INF下新建配置文件persistence.xml,这个配置文件到时候会自动被Hibernate读取。

persistence.xml

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
             version="2.0">
    <persistence-unit name="cn.derker">
        <class>cn.derker.model.User</class>
        <properties>
            <property name="javax.persistence.jdbc.driver" value=http://www.mamicode.com/"com.mysql.cj.jdbc.Driver"/>
            <property name="javax.persistence.jdbc.url" value=http://www.mamicode.com/"jdbc:mysql://localhost/test?useUnicode=true&amp;characterEncoding=UTF-8"/>
            <property name="javax.persistence.jdbc.user" value=http://www.mamicode.com/"root"/>
            <property name="javax.persistence.jdbc.password" value=http://www.mamicode.com/"root"/>
            <property name="hibernate.show_sql" value=http://www.mamicode.com/"true"/>
            <property name="hibernate.hbm2ddl.auto" value=http://www.mamicode.com/"update"/>
        </properties>
    </persistence-unit>
</persistence>

配置项根据名称应该能大致猜出是什么意思。可以看出我测试的是MySQL数据库。解释一下几个配置的意思:persistence-unitname属性的值是自己定义的,但之后在Java代码中会用到。class标签的内容是需要持久化的对象的全类名;hibernate.hbm2ddl.auto配置项的取值和含义如下:

  • create:启动的时候先drop定义的持久化类对应的表,再重新create,这样表中的数据全没了,慎用!!!
  • create-drop: 启动时create表,关闭时drop表;
  • update: 启动时会去检查各个表的schema是否一致,如果不一致会做更新表结构
  • validate: 启动时验证各个表的schema与在hibernate中定义的持久化类数据结构信息是否一致。如果不一致就抛出异常,并不做更新。

在以上配置中,我们告诉了hibernate去连接哪一个数据库,包括连接数据库的用户名,密码,以及哪些POJO类需要借助hibernate,但我们没有告诉hibernate如何去定义这些POJO类对应的表结构。hibernate提供了两种方式定义(映射):一种是xml配置;另一种是注解配置。鉴于xml配置不如注解配置方便,下面的例子一律采用注解配置。怎么将User类的结构描述给hibernate呢?如下:

@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    public User() {
    }

    public User(String name) {
        this.name = name;
    }
    // setter、getter方法略
}

首先是在类的名称上声明了@Entity注解;然后在id字段上声明了@Id@GeneratedValue(strategy = GenerationType.IDENTITY)。其中@Id表明把id字段定义为主键;@GeneratedValue说明了主键的生成策略是自动增长的(当然,这需要你使用的数据库支持这一特性)。这样所有的配置就搞定了。

接下来就可以测试CURD操作了,直接贴出测试类的代码:

public class TestApp {

    private EntityManagerFactory entityManagerFactory;

    @Before
    public void init() {
        // cn.derker与上面配置的persistence.xml中persistence-unit的name值一致
        entityManagerFactory = Persistence.createEntityManagerFactory("cn.derker");
    }
    
    @After
    public void destroy() throws Exception {
        entityManagerFactory.close();
    }

    @Test
    public void testSave() throws Exception {
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        entityManager.getTransaction().begin();
        entityManager.persist(new User("张三")); // 为User添加了一个有参的构造方法,但千万别忘了声明一个无参的构造方法
        entityManager.getTransaction().commit();
    }

    @Test
    public void testFind() throws Exception {
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        entityManager.getTransaction().begin();
        User user = entityManager.find(User.class, 1L);
        Assert.assertEquals("张三", user.getName());
        entityManager.getTransaction().commit();
    }

    @Test
    public void testUpdate() throws Exception {
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        entityManager.getTransaction().begin();
        User user = entityManager.find(User.class, 1L);
        user.setName("李四"); // 这里不必再调用update方法,hibernate会自动更新
        entityManager.getTransaction().commit();
    }

    @Test
    public void testDelete() throws Exception {
        EntityManager entityManager = entityManagerFactory.createEntityManager();
        entityManager.getTransaction().begin();
        User user = entityManager.find(User.class, 1L);
        entityManager.remove(user);
        entityManager.getTransaction().commit();
    }
}

2.2 映射类型

Hibernate将数据的类型分为两大类:value typesentity types。value types类型的数据依附于entity typed类型的数据。举个栗子:

class User{
    Long id;
    String name;
    Address address;
}

User便是entity type,它有自己的生命周期;而想Long、String类型的id、name包括Address类型的address都是value type,它们不会单独存在,而是依附于User。

显然,value type就包括3种类型的数据,一种是basic types,比如一些表示数值、表示字符、表示日期的类型等(见这里);第二种是embeddable types,像上面例子中的Address类型;最后还有一种是collection types,这很明显。

关于basic typesembeddable types之后小节会详细介绍。

2.3 命名策略

如果你运行了上面入门中的栗子,你会发现Hibernate默认帮我们建立的表的表名和列名与我们在类中定义的名称是一致的,这是Hibernate的默认命名策略。如果我们想自定义表名或者列名,有两种方法可以实现。

一种是被称为ImplicitNamingStrategy的方案。做法很简单,只需要使用@Table@Column注解即可,其中name属性的值就是表或列的名字,栗子如下:

@Entity
@Table(name = "tb_user")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(name = "col_name")
    private String name;
    ...
}

另一种方案叫做PhysicalNamingStrategy,这种方案可以进行“大批量”地命名。比如有些人习惯将表名命名成"tb_" + 蛇形命名的类名,将列名命名为"col_" + 蛇形命名的字段名,如果采用上一种命名策略方案将会写大量的注解,若采用该命名策略方案则只需定义一个类即可,该类需要实现PhysicalNamingStrategy接口,栗子如下:

public class MyNamingStrategy implements PhysicalNamingStrategy {

    @Override
    public Identifier toPhysicalCatalogName(Identifier name, JdbcEnvironment jdbcEnvironment) {
        return name;
    }

    @Override
    public Identifier toPhysicalSchemaName(Identifier name, JdbcEnvironment jdbcEnvironment) {
        return name;
    }

    @Override
    public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment jdbcEnvironment) {
        return jdbcEnvironment.getIdentifierHelper().toIdentifier("tb_" + camel2Snake(name.getText()));
    }

    @Override
    public Identifier toPhysicalSequenceName(Identifier name, JdbcEnvironment jdbcEnvironment) {
        return name;
    }

    @Override
    public Identifier toPhysicalColumnName(Identifier name, JdbcEnvironment jdbcEnvironment) {
        if ("id".equals(name.getText())) {
            return name;
        }
        return jdbcEnvironment.getIdentifierHelper().toIdentifier("col_" + camel2Snake(name.getText()));
    }

    /**
     * 驼峰命名转蛇形命名
     */
    private String camel2Snake(String name) {
        StringBuilder sb = new StringBuilder(name.length() * 2);
        for (int i = 0; i < name.length(); i++) {
            char c = name.charAt(i);
            if(c == ‘ ‘){
                sb.append(‘_‘);
                continue;
            }
            if (Character.isUpperCase(c)) {
                if (i != 0) {
                    sb.append(‘_‘);
                }
                sb.append(Character.toLowerCase(c));
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }
}

以上代码主要是在toPhysicalTableNametoPhysicalColumnName方法中做了“手脚”,它们的返回值分别决定了表名和列名。

定义了MyNamingStrategy后还需要将它“告诉”Hibernate,很简单,只需要在配置文件persistence.xml中加上一个配置参数就搞定了:

<properties>
    <property name="hibernate.physical_naming_strategy" value=http://www.mamicode.com/"cn.derker.strategy.MyNamingStrategy"/>
</properties>

2.4 Basic Types

严格地讲,basic types 类型的数据必须要使用@Basic注解,但是我们之前说过,basic types 类型包括表示数值,表示字符,表示时间等数据类型,这是我们经常大量定义的类型,因此@Basic可以省略不写,它默认被假定。一个严格的User

@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic
    private Long id;
    @Basic
    private String name;
    ...
}

事实上,@Basic注解还有两个属性:

  1. optional - boolean (defaults to true):定义是否允许值为null
  2. fetch - FetchType (defaults to EAGER):定义是否采用懒加载策略。FetchType.EAGER表示不使用;FetchType.LAZY表示使用。事实上,对于basic type,Hibernate实现的时候忽略了该属性

我们知道Java的基本数据类型和Sql的数据类型不是一一对应的,存在着一些差别,而在上面的栗子中我们并没有“告诉”Hibernate如何去处理这些差异带来的问题,Hibernate是如何知道怎么将Java的某种基本数据类型转换成Sql数据类型呢?其实在Hibernate中已经定义好了许多这样实现数据类型转换功能的类,比如针对java.lang.Stringorg.hibernate.type.StringType,处理java.lang.Integerorg.hibernate.type.IntegerType。这启示我们,可以写一个类似这样的类,去处理一些Hibernate还不支持的Java类型的转换,比如下面的栗子,自定义一个basic type,可以将String[]类型的数据以Json的字符串的形式保存在数据库中,并且在读取时自动还原成之前的字符数组。

为此,我们为之前的User对象添加一个hobbies(爱好)属性:

public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @Type(type = "String[]")
    private String[] hobbies;

    public User() {
    }

    public User(String name, String[] hobbies) {
        this.name = name;
        this.hobbies = hobbies;
    }
    // 省略setter、getter、toString方法
}

注意,我们为String[] hobbies加上了org.hibernate.annotations.Type注解,并且指明了需要使用名叫“String[]”的Hibernate基本类型去处理。这个"String[]"命名是随意的,但要与我们定义时的名字一致。下面来定义这个basic type:

public class StringArrayType extends AbstractSingleColumnStandardBasicType<String[]>
        implements DiscriminatorType<String[]> {

    public static final StringArrayType INSTANCE = new StringArrayType();

    public StringArrayType() {
        super(VarcharTypeDescriptor.INSTANCE, StringArrayTypeDescriptor.INSTANCE);
    }

    @Override
    public String[] stringToObject(String xml) throws Exception {
        return fromString( xml );
    }

    @Override
    public String objectToSQLString(String[] value, Dialect dialect) throws Exception {
        return toString(value);
    }

    @Override
    public String getName() {
        return "String[]";
    }
}

观察以上代码,我们定义了一个StringArrayType类,它继承自AbstractSingleColumnStandardBasicType,实现了DiscriminatorType接口,泛型参数类型为我们要处理的Java类型String[],并且该类还采用了单例模式(饿汉式),这是自定义基本类型的“标准做法”。在构造方法中,调用了父类的构造方法,两个参数分别是SqlTypeDescriptor(Sql类型描述符)和JavaTypeDescriptor(描述符),代表需要在Sql的varchar类型和Java的String[]类型间做转换。接下去实现了三个方法,其中stringToObject方法是用来指明如何将discriminator-value(这个以后会说明)的值转换为我们的需要处理的Java类型;objectToSQLString方法用来指明如何将我们需要处理的类型的对象转化为Sql语句(片段)。这两个方法都是直接调用了父类的fromStringtoString方法,事实上在父类中,这两个方法调用了我们之前在构造函数中给定的 JavaTypeDescriptor的fromStringtoString方法;最后还实现的一个方法是getName,该方法用来定义该类型的名称,即之前我们在Type注释中使用的。

接下来需要实现上面使用的JavaTypeDescriptor——StringArrayTypeDescriptor:

public class StringArrayTypeDescriptor extends AbstractTypeDescriptor<String[]> {

    public static final StringArrayTypeDescriptor INSTANCE = new StringArrayTypeDescriptor();

    protected StringArrayTypeDescriptor() {
        super(String[].class);
    }

    @Override
    public String toString(String[] value) {
        return JSON.toJSONString(value);
    }

    @Override
    public String[] fromString(String string) {
        JSONArray jsonArray = JSON.parseArray(string);
        String[] result = new String[jsonArray.size()];
        for (int i = 0; i < result.length; i++) {
            result[i] = jsonArray.getString(i);
        }
        return result;
    }

    @Override
    @SuppressWarnings({"unchecked"})
    public <X> X unwrap(String[] value, Class<X> type, WrapperOptions options) {
        if (value =http://www.mamicode.com/= null) {
            return null;
        }
        if (type != String.class) {
            throw unknownUnwrap(type);
        }
        return (X) toString(value);
    }

    @Override
    public <X> String[] wrap(X value, WrapperOptions options) {
        if (!(value instanceof String)) {
            throw unknownWrap(value.getClass());
        }
        return fromString((String) value);
    }
}

该类也采用单例模式,需要实现toStringfromStringunwrapwrap方法。toStringfromString方法指定我们需要处理的类与String之间如何转换,这也是上面提到的被调用的JavaTypeDescriptor的toStringfromString方法;unwrap方法在为String[]类型字段指定PreparedStatement的参数时调用;wrap方法相反,是表明如何将从数据库中取出的值转换为需要处理的Java类型。

在其中,我们用到了阿里的fastjson库,需要在Maven的pom.xml中添加依赖:

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>fastjson</artifactId>
  <version>1.2.24</version>
</dependency>

最后还需要将我们定义的这个基本类型“告诉”hibernate。“告诉”的方式官方给出了两种,但都是用Hibernate原生的API调用的,我暂时还没有找到用JPA怎么添加基本类型的,如果你知道,请告诉我。

这里就采用原生的API来举例吧。我们需要添加新的配置文件hibernate.cfg.xml,该文件位于resources目录或类的根路径下,内容和之前JPA使用的persistence.xml类似,如下:

<?xml version=‘1.0‘ encoding=‘utf-8‘?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">

<hibernate-configuration>
    <session-factory>
        <property name="connection.driver_class">com.mysql.cj.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost/test?useUnicode=true&amp;characterEncoding=UTF-8
        </property>
        <property name="connection.username">root</property>
        <property name="connection.password">root</property>
        <property name="connection.pool_size">5</property>
        <property name="dialect">org.hibernate.dialect.MySQL57InnoDBDialect</property>
        <property name="cache.provider_class">org.hibernate.cache.internal.NoCacheProvider</property>
        <property name="show_sql">true</property>
        <property name="hbm2ddl.auto">update</property>
        <property name="hibernate.physical_naming_strategy">cn.derker.strategy.MyNamingStrategy</property>
        
        <mapping class="cn.derker.model.User"/>
    </session-factory>
</hibernate-configuration>

之后在Java代码中我们这样将定义的StringArrayType添加到hibernate中:

@Before
public void init() {
  final StandardServiceRegistry registry = new StandardServiceRegistryBuilder()
                    .configure() // configures settings from hibernate.cfg.xml
                    .build();
  MetadataSources sources = new MetadataSources(registry);
  MetadataBuilder metadataBuilder = sources.getMetadataBuilder();
  metadataBuilder.applyBasicType(StringArrayType.INSTANCE);
  sessionFactory = metadataBuilder.build().buildSessionFactory();
}

hibernate原生API的sessionFactory与JPA的EntityManager类似,有了它之后,可以这样调用添加一个用户:

@Test
public void testSave() throws Exception {
    Session session = sessionFactory.openSession();
    session.getTransaction().begin();
    User user = new User("张三", new String[]{"吃饭", "睡觉", "打豆豆"});
    session.persist(user);
    session.getTransaction().commit();
    session.close();
}

这样就可以将一个Java字符数组类型的字段以json字符的形式(sql中是varchar类型)保存在数据库中。其他更新、查找、删除方法就不一一给出了,总之都是通过sessionFactory.openSession()得到一个Session对象,然后调用session对想的相关方法。Session对象与JPA中的EntityManager是类似的。

3. 小结

以上内容除了对hibernate的一些基本概念的介绍外,主要给出了三个问题的解答:

  1. 如何使用Hibernate对一个对象进行最基本的CURD操作;
  2. 如何自定义表名,列名;
  3. 如何自定义Hibernate基本类型处理“非一般”Java类型,如处理String[]类型;

Hibernate学习笔记