#持久化框架操作示例(jpa-hibernate)

# 环境说明

以使用Maven为例:

Java环境:Jdk8

Springboot版本:1.5.6.RELEASE

Maven版本:3.8.6

hibernate-core版本:5.0.12.Final

崖山驱动版本:1.3.0

崖山方言包版本:1.0.0

Maven的pom.xml文件:(用于验证整个jpa-hibernate与YashanDB的兼容性,所以涵盖很多的依赖包,若只需其中部分功能,保留相应的依赖即可。)

 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    
        <!-- spring jpa,里面包含了hibernate核心类  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
    
        <!-- lombok  -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    
    </dependencies>

# 添加YashanDB驱动和方言包

打开Idea的ProjectStructure,使用Libraries添加YashanDB驱动,对于多模块,只需要添加到对应的模块即可。(YashanDB驱动:在产品软件包或安装目录的Drivers文件夹中,查找yasdb-jdbc-版本号.jar文件)

也可以将YashanDB驱动上传到本地的Maven私服,然后在Pom文件中引入。

用同样的方式去添加崖山的方言包。(方言包:在产品软件包中,文件名为YashanDialect-for-hibernate5-版本号.jar,例如YashanDialect-for-hibernate5-1.0.0.jar)

# 数据源和jpa-hibernate配置

本文采用application.properties文件进行参数配置。

请将下例中的host_ip、port、dbname、username和password修改为实际值,spring.jpa.properties.hibernate.dialect则必须设置为org.hibernate.dialect.YasDialect。

spring.datasource.url=jdbc:yasdb://127.0.0.1:1688/yasdb
spring.datasource.username=regress
spring.datasource.password=regress
spring.datasource.driver-class-name=com.yashandb.jdbc.Driver

# 方言必须设置为崖山方言YasDialect
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.YasDialect

# 是否自动建表
spring.jpa.properties.hibernate.hbm2ddl.auto=create

# 字段名和数据库中对象名的映射关系:驼峰转下划线
spring.jpa.properties.hibernate.naming-strategy = org.hibernate.cfg.ImprovedNamingStrategy

# jpa-hibernate功能验证

# 创建entity

创建User用户的entity,并使其与Table对应。


import lombok.Data;
import org.hibernate.annotations.ColumnDefault;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import java.time.LocalTime;

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

    @Id // hibernate要求必须要一个id(主键),不然会报错
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "ID_GENERATOR")
    @SequenceGenerator(name = "ID_GENERATOR", sequenceName = "user_sequence")
    @ColumnDefault("user_sequence.nextval")
    private int id;
    
    @Column(name = "name")
    private String name;
    
    public User() {
    }

    public User(String name) {
        this.name = name;
    }
    
    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }
}

定义Repository接口,可以继承JpaRepository,也可以继承CrudRepository,视个人使用习惯或团队要求自由选择,本例中使用JpaRepository:

import com.example.pojo.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User,Integer> {
// 注意JpaRepository<User,Integer>这里传入的泛型类型,第一个是我们上面定义的Entity,第二个是Entity的主键的类型
// JpaRepository中可以不实现任何接口,其默认接口已足够使用。
}

# 创建单元测试类

package com.example;

import com.example.dao.UserRepository;
import com.example.pojo.User;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.List;

import static org.junit.Assert.assertEquals;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserTest {
    @Autowired
    private UserRepository repository;
    @Before
    public void deleteAllUser(){
        repository.deleteAll();
    }

    @Test
    public void testAddUser() {
        User user = new User("sasa");
        repository.save(user);
        List<User> list = repository.findAll();
        assertEquals(1, list.size());
    }
}

# 常见问题

# 自增主键的使用

一般情况下,在使用Hibernate持久化框架时,需定义Entity实体类,实体类中存在一个Id属性,将该属性作为业务表的自增主键列,示例如下:

@Id
@SequenceGenerator(name="generator",sequenceName="user_sequence", allocationSize =1)
@ColumnDefault("user_sequence.nextval")
@GeneratedValue(strategy= GenerationType.AUTO, generator = "generator")
private int id;

在上述示例中,user_sequence为序列名,可自定义命名。

Note:

  • 请注意保持@SequenceGenerator参数sequenceName和@ColumnDefault中的序列名称一致。

  • 如果sequenceName指定的序列名是驼峰格式,hibernate会把它改成下划线,与表名、列名等一样处理;但@ColumnDefault这个注解的value是不处理驼峰格式的,会导致sequenceName和ColumnDefault里面序列器名称不一致,启动时建表会失败。因此,此处不能用驼峰命名,应直接用下划线风格命名。

此外,也可以不指定@ColumnDefault和@SequenceGenerator,此时hibernate使用默认序列hibernate_sequence进行自增,示例如下:

@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private int id;

# 服务启动失败

使用hibernate配置hbm2ddl.auto=validate时,服务启动时会进行校验;当数据库表结构数据类型与Java实体类数据类型不一致时,Schema-validation校验失败;从而导致容器初始化Hibernate SessionFactory时失败。

spring.jpa.properties.hibernate.hbm2ddl.auto=validate

出现这个问题时,解决方案如下:

  • 方案一:需要修改数据库表结构或Java实体类数据类型,保持数据库表结构与Java实体类数据类型一致。

  • 方案二:将hbm2ddl.auto配置设置为none,服务启动时不进行校验。

# 数据类型差异

MySQL与YashanDB存在数据类型差异,例如MySQL中存在TEXT数据类型而YashanDB无。

迁移前,业务中可能使用了此类差异化数据类型TEXT,示例如下:

@Column(name = "file_types", columnDefinition = "TEXT")
private String fileTypes;

迁移后,会因YashanDB中不存在相同的数据类型而报错,在YashanDB中存在CLOB类型(同样可用于保存大量字符数据),可使用CLOB类型代替MySQL中的TEXT数据类型,即将columnDefinition = "TEXT"改为columnDefinition = "CLOB"。