YDB dialect for Hibernate
Overview
This is a guide to using Hibernate with YDB.
Hibernate is an Object-Relational Mapping (ORM) framework for Java that facilitates the mapping of object-oriented models to SQL.
Installation
Add the following dependency to your project:
<!-- Set actual versions -->
<dependency>
<groupId>tech.ydb.jdbc</groupId>
<artifactId>ydb-jdbc-driver</artifactId>
<version>${ydb.jdbc.version}</version>
</dependency>
<dependency>
<groupId>tech.ydb.dialects</groupId>
<artifactId>hibernate-ydb-dialect</artifactId>
<version>${hibernate.ydb.dialect.version}</version>
</dependency>
dependencies {
// Set actual versions
implementation "tech.ydb.dialects:hibernate-ydb-dialect:$ydbDialectVersion"
implementation "tech.ydb.jdbc:ydb-jdbc-driver:$ydbJdbcVersion"
}
If you use Hibernate version 5, you need <artifactId>hibernate-ydb-dialect-v5</artifactId>
for Maven or implementation 'tech.ydb.dialects:hibernate-ydb-dialect-v5:$version
for Gradle instead of the similar package without the -v5
suffix.
Configuration
Configure Hibernate to use the custom YDB dialect by updating your persistence.xml file:
<property name="hibernate.dialect">tech.ydb.hibernate.dialect.YdbDialect</property>
Or, if you are using programmatic configuration:
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Configuration;
public static Configuration basedConfiguration() {
return new Configuration()
.setProperty(AvailableSettings.JAKARTA_JDBC_DRIVER, YdbDriver.class.getName())
.setProperty(AvailableSettings.DIALECT, YdbDialect.class.getName());
}
import org.hibernate.cfg.AvailableSettings
import org.hibernate.cfg.Configuration
fun basedConfiguration(): Configuration = Configuration().apply {
setProperty(AvailableSettings.JAKARTA_JDBC_DRIVER, YdbDriver::class.name)
setProperty(AvailableSettings.DIALECT, YdbDialect::class.name)
}
Usage
Use this custom dialect just like any other Hibernate dialect. Map your entity classes to database tables and use Hibernate's session factory to perform database operations.
Table of comparison of Java types descriptions with YDB types:
Java type | YDB type |
---|---|
bool , Boolean |
Bool |
String , enum with annotation @Enumerated(EnumType.STRING) |
Text (synonym Utf8 ) |
java.time.LocalDate |
Date |
java.math.BigDecimal , java.math.BigInteger |
Decimal(22,9) |
double , Double |
Double |
float , Float |
Float |
int , java.lang.Integer |
Int32 |
long , java.lang.Long |
Int64 |
short , java.lang.Short |
Int16 |
byte , java.lang.Byte , enum with annotation @Enumerated(EnumType.ORDINAL) |
Int8 |
[]byte |
Bytes (synonym String ) |
java.time.LocalDateTime (timezone will be set to UTC ) |
Datetime |
java.time.Instant (timezone will be set to UTC ) |
Timestamp |
YDB dialect supports database schema generation based on Hibernate entities.
For example, for the Group
class:
@Getter
@Setter
@Entity
@Table(name = "Groups", indexes = @Index(name = "group_name_index", columnList = "GroupName"))
public class Group {
@Id
@Column(name = "GroupId")
private int id;
@Column(name = "GroupName")
private String name;
@OneToMany(mappedBy = "group")
private List<Student> students;
}
@Entity
@Table(name = "Groups", indexes = [Index(name = "group_name_index", columnList = "GroupName")])
data class Group(
@Id
@Column(name = "GroupId")
val id: Int,
@Column(name = "GroupName")
val name: String,
@OneToMany(mappedBy = "group")
val students: List<Student>
)
The following Groups
table will be created, and the GroupName
will be indexed by a global secondary index named group_name_index
:
CREATE TABLE Groups (
GroupId Int32 NOT NULL,
GroupName Text,
PRIMARY KEY (GroupId)
);
ALTER TABLE Groups
ADD INDEX group_name_index GLOBAL
ON (GroupName);
If you evolve the Group entity by adding the deparment
field:
@Column
private String department;
@Column
val department: String
At the start of the application, Hibernate will update the database schema if the update
mode is set in properties:
jakarta.persistence.schema-generation.database.action=update
The result of changing the schema is:
ALTER TABLE Groups
ADD COLUMN department Text
Warning
YDB dialect supports @OneToMany
, @ManyToOne
and @ManyToMany
relationships.
For example, for @OneToMany
generates a SQL script:
SELECT
g1_0.GroupId,
g1_0.GroupName
FROM
Groups g1_0
WHERE
g1_0.GroupName='M3439'
SELECT
s1_0.GroupId,
s1_0.StudentId,
s1_0.StudentName
FROM
Students s1_0
WHERE
s1_0.GroupId=?
SELECT
g1_0.GroupId,
g1_0.GroupName,
s1_0.GroupId,
s1_0.StudentId,
s1_0.StudentName
FROM
Groups g1_0
JOIN
Students s1_0
on g1_0.GroupId=s1_0.GroupId
WHERE
g1_0.GroupName='M3439'
Example with Spring Data JPA
Configure Spring Data JPA with Hibernate to use custom YDB dialect by updating your application.properties
:
spring.jpa.properties.hibernate.dialect=tech.ydb.hibernate.dialect.YdbDialect
spring.datasource.driver-class-name=tech.ydb.jdbc.YdbDriver
spring.datasource.url=jdbc:ydb:<grpc/grpcs>://<host>:<2135/2136>/path/to/database[?saFile=file:~/sa_key.json]
Create a simple entity and repository:
@Data
@Entity
@Table(name = "employee")
public class Employee {
@Id
private long id;
@Column(name = "full_name")
private String fullName;
@Column
private String email;
@Column(name = "hire_date")
private LocalDate hireDate;
@Column
private java.math.BigDecimal salary;
@Column(name = "is_active")
private boolean isActive;
@Column
private String department;
@Column
private int age;
}
public interface EmployeeRepository implements CrudRepository<Employee, Long> {}
@Entity
@Table(name = "employee")
data class Employee(
@Id
val id: Long,
@Column(name = "full_name")
val fullName: String,
@Column
val email: String,
@Column(name = "hire_date")
val hireDate: LocalDate,
@Column
val salary: java.math.BigDecimal,
@Column(name = "is_active")
val isActive: Boolean,
@Column
val department: String,
@Column
val age: Int,
)
interface EmployeeRepository : CrudRepository<Employee, Long>
fun EmployeeRepository.findByIdOrNull(id: Long): Employee? = this.findById(id).orElse(null)
Usage example:
Employee employee = new Employee(
1,
"Example",
"[email protected]",
LocalDate.parse("2023-12-20"),
BigDecimal("500000.000000000"),
true,
"YDB AppTeam",
23
);
/* The following SQL will be executed:
INSERT INTO employee (age,department,email,full_name,hire_date,is_active,limit_date_password,salary,id)
VALUES (?,?,?,?,?,?,?,?,?)
*/
employeeRepository.save(employee);
assertEquals(employee, employeeRepository.findById(employee.getId()).get());
/* The following SQL will be executed:
DELETE FROM employee WHERE id=?
*/
employeeRepository.delete(employee);
assertNull(employeeRepository.findById(employee.getId()).orElse(null));
val employee = Employee(
1,
"Example",
"[email protected]",
LocalDate.parse("2023-12-20"),
BigDecimal("500000.000000000"),
true,
"YDB AppTeam",
23
)
/* The following SQL will be executed:
INSERT INTO employee (age,department,email,full_name,hire_date,is_active,limit_date_password,salary,id)
VALUES (?,?,?,?,?,?,?,?,?)
*/
employeeRepository.save(employee)
assertEquals(employee, employeeRepository.findByIdOrNull(employee.id))
/* The following SQL will be executed:
DELETE FROM employee WHERE id=?
*/
employeeRepository.delete(employee)
assertNull(employeeRepository.findByIdOrNull(employee.id))
An example of a simple Spring Data JPA repository can be found at the following link.