JDBC
JDBC 持久化模块针对关系型数据库(RDBMS)数据存取的一套简单解决方案,主要关注数据存取的效率、易用性、稳定和透明,其具备以下功能特征:
- 基于 JDBC 框架 API 进行轻量封装,结构简单、便于开发、调试和维护;
- 优化批量数据更新、标准化结果集、预编译 SQL 语句处理;
- 支持单实体 ORM 操作,无需编写 SQL 语句;
- 提供脚手架工具,快速生成数据实体类,支持链式调用;
- 支持通过存储器注解自定义 SQL 语句或从配置文件中动态加载 SQL 并自动执行;
- 支持结果集与值对象的自动装配,支持自定义装配规则;
- 支持多数据源,默认支持 C3P0、DBCP、Druid、HikariCP、JNDI 连接池配置,支持数据源扩展;
- 支持多种数据库(如:Oracle、MySQL、SQLServer、SQLite、H2、PostgreSQL 等);
- 支持面向对象的数据库查询封装,有助于减少或降低程序编译期错误;
- 支持数据库事务嵌套;
- 支持数据库视图和存储过程;
Maven包依赖
<dependency>
<groupId>net.ymate.platform</groupId>
<artifactId>ymate-platform-persistence-jdbc</artifactId>
<version>2.1.2</version>
</dependency>
模块配置
配置文件参数说明
#-------------------------------------
# JDBC持久化模块初始化参数
#-------------------------------------
# 默认数据源名称, 默认值: default
ymp.configs.persistence.jdbc.ds_default_name=
# 数据源列表, 多个数据源名称间用'|'分隔, 默认值: default
ymp.configs.persistence.jdbc.ds_name_list=
# 是否自动连接, 即模块初始化时完成连接动作, 默认值: false
ymp.configs.persistence.jdbc.ds.default.auto_connection=true
# 是否显示执行的SQL语句, 默认值: false
ymp.configs.persistence.jdbc.ds.default.show_sql=true
# 是否开启堆栈跟踪, 默认值: false
ymp.configs.persistence.jdbc.ds.default.stack_traces=true
# 堆栈跟踪层级深度, 默认值: 0(即全部)
ymp.configs.persistence.jdbc.ds.default.stack_trace_depth=
# 堆栈跟踪包名前缀过滤, 默认值: 空
ymp.configs.persistence.jdbc.ds.default.stack_trace_packages=
# 自定义引用标识符, 根据数据库类型进行设置, 默认值: 空
ymp.configs.persistence.jdbc.ds.default.identifier_quote=
# 数据库表前缀名称, 多个前缀名称间用'|'分隔, 默认值: 空
ymp.configs.persistence.jdbc.ds.default.table_prefix=
# 数据源适配器, 可选值为已知适配器名称或自定义适配置类名称, 默认值: default, 目前支持已知适配器[default|dbcp|c3p0|druid|hikaricp|jndi|...]
ymp.configs.persistence.jdbc.ds.default.adapter_class=dbcp
# 数据源适配器配置文件,可选参数,若未设置或设置的文件路径无效将被忽略,默认值为空
ymp.configs.persistence.jdbc.ds.default.config_file=
# 数据库类型, 可选参数, 默认值将通过连接字符串分析获得, 目前支持[mysql|oracle|sqlserver|db2|sqlite|postgresql|hsqldb|h2]
ymp.configs.persistence.jdbc.ds.default.type=
# 数据库方言, 可选参数, 自定义方言将覆盖默认配置
ymp.configs.persistence.jdbc.ds.default.dialect_class=
# 数据库连接驱动, 可选参数, 框架默认将根据数据库类型进行自动匹配
ymp.configs.persistence.jdbc.ds.default.driver_class=
# 数据库连接字符串, 必填参数
ymp.configs.persistence.jdbc.ds.default.connection_url=jdbc:mysql://localhost:3306/db_name?useUnicode=true&useSSL=false&characterEncoding=UTF-8
# 数据库访问用户名称, 必填参数
ymp.configs.persistence.jdbc.ds.default.username=root
# 数据库访问密码, 可选参数, 经过默认密码处理器加密后的admin字符串为wRI2rASW58E
ymp.configs.persistence.jdbc.ds.default.password=wRI2rASW58E
# 数据库访问密码是否已加密, 默认值: false
ymp.configs.persistence.jdbc.ds.default.password_encrypted=true
# 数据库密码处理器, 可选参数, 用于对已加密码数据库访问密码进行解密, 默认值: 空
ymp.configs.persistence.jdbc.ds.default.password_class=
配置注解参数说明
当 JDBC 持久化模块初始化时,若在配置文件中存在数据源相关配置,则基于注解的数据源配置将全部失效。
@DatabaseConf
配置项 | 描述 |
---|---|
dsDefaultName | 默认数据源名称 |
value | 数据源配置 |
@DatabaseDataSource
配置 项 | 描述 |
---|---|
name | 数据源名称 |
connectionUrl | 数据库连接字符串 |
username | 数据库访问用户名称 |
password | 数据库访问密码 |
passwordEncrypted | 数据库访问密码是否已加密 |
passwordClass | 数据库密码处理器 |
type | 数据库类型 |
dialectClass | 数据库方言 |
adapterClass | 数据源适配器 |
configFile | 数据源适配器配置文件 |
driverClass | 数据库默认驱动类名称 |
autoConnection | 是否自动连接 |
showSql | 是否显示执行的 SQL 语句 |
stackTraces | 是否开启堆栈跟踪 |
stackTraceDepth | 堆栈跟踪层级深度 |
stackTracePackages | 堆栈跟踪过滤包名前缀集合 |
tablePrefix | 数据库表前缀名称 |
identifierQuote | 数据库引用标识符 |
数据源(DataSource)
多数据源连接
JDBC 持久化模块默认支持多数据源配置,下面通过简单的配置来展示如何连接多个数据库:
# 定义两个数据源分别用于连接MySQL和Oracle数据库,同时指定默认数据源为default(即MySQL数据库)
ymp.configs.persistence.jdbc.ds_default_name=default
ymp.configs.persistence.jdbc.ds_name_list=default|oracledb
# 连接到MySQL数据库的数据源配置
ymp.configs.persistence.jdbc.ds.default.connection_url=jdbc:mysql://localhost:3306/mydb
ymp.configs.persistence.jdbc.ds.default.username=root
ymp.configs.persistence.jdbc.ds.default.password=123456
# 连接到Oracle数据库的数据源配置
ymp.configs.persistence.jdbc.ds.oracledb.connection_url=jdbc:oracle:thin:@localhost:1521:ORCL
ymp.configs.persistence.jdbc.ds.oracledb.username=ORCL
ymp.configs.persistence.jdbc.ds.oracledb.password=123456
从上述配置中可以看出,配置不同的数据源时只需要定义数据源名称列表,再根据列表逐一配置即可。
通过注解方式配置多数据源,如下所示:
@DatabaseConf(dsDefaultName = "default", value = {
@DatabaseDataSource(name = "default",
connectionUrl = "jdbc:mysql://localhost:3306/mydb",
username = "root",
password = "123456"),
@DatabaseDataSource(name = "oracledb",
connectionUrl = "jdbc:oracle:thin:@localhost:1521:ORCL",
username = "ORCL",
password = "123456")
})
连接池配置
JDBC 持久化模块提供的数据源类型如下:
名称 | 类型 | 描述 |
---|---|---|
default | DefaultDataSourceAdapter | 默认数据源适配器,通过 DriverManager 直接连接数据库,建议仅用于测试。 |
c3p0 | C3P0DataSourceAdapter | 基于 C3P0 连接池的数据源适配器。 |
dbcp | DBCPDataSourceAdapter | 基于 DBCP 连接池的数据源适配器。 |
druid | DruidDataSourceAdapter | 基于阿里巴巴开源的 Druid 连接池的数据源适配器。 |
hikaricp | HikariCPDataSourceAdapter | 基于 HikariCP 连接池的数据源适配器。 |
jndi | JNDIDataSourceAdapter | 基于 JNDI 的数据源适配器。 |
只需根据实际情况调整对应数据源名称的配置,如:
ymp.configs.persistence.jdbc.ds.default.adapter_class=dbcp
通过注解配置方式,如下所示:
@DatabaseConf(dsDefaultName = "default",
value = {
@DatabaseDataSource(name = "default",
connectionUrl = "jdbc:mysql://localhost:3306/mydb",
username = "root",
password = "123456",
adapterClass = DBCPDataSourceAdapter.class)
})
针对于 dbcp、druid、hikaricp 和 c3p0 连接池的配置文件及内容,请将对应的配置文件(如:dbcp.properties
、c3p0.properties
等)放置在工程的 classpath
根路径下,若上述配置文件不存在,JDBC 持久化模块在初始化时将自动创建。
另外,dbcp、druid、hikaricp 和 c3p0 连接池支持根据数据源名称进行单独配置(如:dbcp_oracledb.properties
,此文件将优先于 dbcp.properties
被加载),其中 druid 连接池可以兼容 dbcp 连接池的配置文件。
当然,也可以通过 IDatabaseDataSourceAdapter
接口自行实现,框架针对该接口提供了 AbstractDatabaseDataSourceAdapter
抽象类,直接继承即可。
数据库连接持有者(IDatabaseConnectionHolder)
用于记录真正的数据库连接对象(Connection)原始的状态及与数据源对应关系,在 JDBC 持久化模块中获取到的所有连接对象均由数据库连接持有者对象包装,基于数据库连接持有者接口可以进行如下操作:
public class Main {
public static void main(String[] args) throws Exception {
try (IApplication application = YMP.run(args)) {
if (application.isInitialized()) {
// 获取当前容器内JDBC模块实例
IDatabase database = application.getModuleManager().getModule(JDBC.class);
// 获取默认数据源的连接持有者实例,等同于:database.getConnectionHolder("default");
IDatabaseConnectionHolder connectionHolder = database.getDefaultConnectionHolder();
// 获取指定名称的数据源连接持有者实例
connectionHolder = database.getConnectionHolder("oracledb");
// 获取连接对象
Connection connection = connectionHolder.getConnection();
// 获取数据源配置对象
IDatabaseDataSourceConfig dataSourceConfig = connectionHolder.getDataSourceConfig();
// 获取当前数据源适配器对象
IDatabaseDataSourceAdapter dataSourceAdapter = connectionHolder.getDataSourceAdapter();
// 获取当前数据库方言
IDialect dialect = connectionHolder.getDialect();
// 获取当前连接持有者所属JDBC模块实例
IDatabase owner = connectionHolder.getOwner();
}
}
}
}
数据实体(Entity)
数据实体是以对象的形式与数据库表之间的一种映射关系,实体中的属性与表中字段一一对应。
数据实体类包含以下几个部份:
- 基本属性:注解配置属性与字段之间的关系及属性的 Getter 和 Setter 方法。
- FIELDS:字段名常量。
- Builder:基本属性构建器类,支持以链式调用方式为实体属性赋值。
- FieldConditionBuilder:属性条件构建器类,为具体实体属性构建字段查询条件。
实体类与表对应关系示例
假定数据库中的 ym_user
数据表结构如下:
CREATE TABLE `ym_user` (
`id` varchar(32) NOT NULL COMMENT '用户唯一标识',
`username` varchar(32) DEFAULT NULL COMMENT '用户名称',
`nickname` varchar(32) DEFAULT NULL COMMENT '昵称',
`gender` varchar(1) DEFAULT NULL COMMENT '性别',
`age` int(2) unsigned DEFAULT '0' COMMENT '年龄',
`avatar_url` varchar(255) DEFAULT NULL COMMENT '头像URL地址',
`password` varchar(32) DEFAULT NULL COMMENT '登录密码',
`email` varchar(100) DEFAULT NULL COMMENT '电子邮件',
`mobile` varchar(20) DEFAULT NULL COMMENT '手机号码',
`type` smallint(2) unsigned DEFAULT '0' COMMENT '类型',
`status` smallint(2) unsigned DEFAULT '0' COMMENT '状态',
`create_time` bigint(13) NOT NULL COMMENT '注册时间',
`last_modify_time` bigint(13) DEFAULT '0' COMMENT '最后修改时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户信息';
通过实体生成工具自动构建的实体类如下:
@Entity(UserEntity.TABLE_NAME)
@Comment("用户信息")
public class UserEntity extends BaseEntity<UserEntity, String> {
private static final long serialVersionUID = 1L;
@Id
@Property(name = FIELDS.ID, nullable = false, length = 32)
@Comment("用户唯一标识")
@PropertyState(propertyName = FIELDS.ID)
private String id;
@Property(name = FIELDS.USERNAME, length = 32)
@Comment("用户名称")
@PropertyState(propertyName = FIELDS.USERNAME)
private String username;
@Property(name = FIELDS.NICKNAME, length = 32)
@Comment("昵称")
@PropertyState(propertyName = FIELDS.NICKNAME)
private String nickname;
@Property(name = FIELDS.GENDER, length = 1)
@Comment("性别")
@PropertyState(propertyName = FIELDS.GENDER)
private String gender;
@Property(name = FIELDS.AGE, length = 2)
@Comment("年龄")
@PropertyState(propertyName = FIELDS.AGE)
private Integer age;
@Property(name = FIELDS.AVATAR_URL, length = 255)
@Comment("头像URL地址")
@PropertyState(propertyName = FIELDS.AVATAR_URL)
private String avatarUrl;
@Property(name = FIELDS.PASSWORD, length = 32)
@Comment("登录密码")
@PropertyState(propertyName = FIELDS.PASSWORD)
private String password;
@Property(name = FIELDS.EMAIL, length = 100)
@Comment("电子邮件")
@PropertyState(propertyName = FIELDS.EMAIL)
private String email;
@Property(name = FIELDS.MOBILE, length = 20)
@Comment("手机号码")
@PropertyState(propertyName = FIELDS.MOBILE)
private String mobile;
@Property(name = FIELDS.TYPE, unsigned = true, length = 2)
@Default("0")
@Comment("类型")
@PropertyState(propertyName = FIELDS.TYPE)
private Integer type;
@Property(name = FIELDS.STATUS, unsigned = true, length = 2)
@Default("0")
@Comment("状态")
@PropertyState(propertyName = FIELDS.STATUS)
private Integer status;
@Property(name = FIELDS.CREATE_TIME, nullable = false, length = 13)
@Comment("注册时间")
@PropertyState(propertyName = FIELDS.CREATE_TIME)
@Readonly
private Long createTime;
@Property(name = FIELDS.LAST_MODIFY_TIME, length = 13)
@Default("0")
@Comment("最后修改时间")
@PropertyState(propertyName = FIELDS.LAST_MODIFY_TIME)
private Long lastModifyTime;
public UserEntity() {
}
public UserEntity(IDatabase dbOwner) {
super(dbOwner);
}
public UserEntity(String id, Long createTime) {
this.id = id;
this.createTime = createTime;
}
public UserEntity(IDatabase dbOwner, String id, Long createTime) {
super(dbOwner);
this.id = id;
this.createTime = createTime;
}
public UserEntity(String id, String username, String nickname, String gender, Integer age, String avatarUrl, String password, String email, String mobile, Integer type, Integer status, Long createTime, Long lastModifyTime) {
this.id = id;
this.username = username;
this.nickname = nickname;
this.gender = gender;
this.age = age;
this.avatarUrl = avatarUrl;
this.password = password;
this.email = email;
this.mobile = mobile;
this.type = type;
this.status = status;
this.createTime = createTime;
this.lastModifyTime = lastModifyTime;
}
public UserEntity(IDatabase dbOwner, String id, String username, String nickname, String gender, Integer age, String avatarUrl, String password, String email, String mobile, Integer type, Integer status, Long createTime, Long lastModifyTime) {
super(dbOwner);
this.id = id;
this.username = username;
this.nickname = nickname;
this.gender = gender;
this.age = age;
this.avatarUrl = avatarUrl;
this.password = password;
this.email = email;
this.mobile = mobile;
this.type = type;
this.status = status;
this.createTime = createTime;
this.lastModifyTime = lastModifyTime;
}
@Override
public String getId() {
return id;
}
@Override
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getAvatarUrl() {
return avatarUrl;
}
public void setAvatarUrl(String avatarUrl) {
this.avatarUrl = avatarUrl;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public Integer getType() {
return type;
}
public void setType(Integer type) {
this.type = type;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public Long getCreateTime() {
return createTime;
}
public void setCreateTime(Long createTime) {
this.createTime = createTime;
}
public Long getLastModifyTime() {
return lastModifyTime;
}
public void setLastModifyTime(Long lastModifyTime) {
this.lastModifyTime = lastModifyTime;
}
public Builder bind() {
return new Builder(this);
}
public static Builder builder() {
return new Builder();
}
public static Builder builder(IDatabase dbOwner) {
return new Builder(dbOwner);
}
public static class Builder {
private final UserEntity targetEntity;
public Builder() {
targetEntity = new UserEntity();
}
public Builder(IDatabase dbOwner) {
targetEntity = new UserEntity(dbOwner);
}
public Builder(UserEntity targetEntity) {
this.targetEntity = targetEntity;
}
public UserEntity build() {
return targetEntity;
}
public IDatabaseConnectionHolder connectionHolder() {
return targetEntity.getConnectionHolder();
}
public Builder connectionHolder(IDatabaseConnectionHolder connectionHolder) {
targetEntity.setConnectionHolder(connectionHolder);
return this;
}
public IDatabase dbOwner() {
return targetEntity.getDbOwner();
}
public Builder dbOwner(IDatabase dbOwner) {
targetEntity.setDbOwner(dbOwner);
return this;
}
public String dataSourceName() {
return targetEntity.getDataSourceName();
}
public Builder dataSourceName(String dataSourceName) {
targetEntity.setDataSourceName(dataSourceName);
return this;
}
public IShardingable shardingable() {
return targetEntity.getShardingable();
}
public Builder shardingable(IShardingable shardingable) {
targetEntity.setShardingable(shardingable);
return this;
}
public String id() {
return targetEntity.getId();
}
public Builder id(String id) {
targetEntity.setId(id);
return this;
}
public String username() {
return targetEntity.getUsername();
}
public Builder username(String username) {
targetEntity.setUsername(username);
return this;
}
public String nickname() {
return targetEntity.getNickname();
}
public Builder nickname(String nickname) {
targetEntity.setNickname(nickname);
return this;
}
public String gender() {
return targetEntity.getGender();
}
public Builder gender(String gender) {
targetEntity.setGender(gender);
return this;
}
public Integer age() {
return targetEntity.getAge();
}
public Builder age(Integer age) {
targetEntity.setAge(age);
return this;
}
public String avatarUrl() {
return targetEntity.getAvatarUrl();
}
public Builder avatarUrl(String avatarUrl) {
targetEntity.setAvatarUrl(avatarUrl);
return this;
}
public String password() {
return targetEntity.getPassword();
}
public Builder password(String password) {
targetEntity.setPassword(password);
return this;
}
public String email() {
return targetEntity.getEmail();
}
public Builder email(String email) {
targetEntity.setEmail(email);
return this;
}
public String mobile() {
return targetEntity.getMobile();
}
public Builder mobile(String mobile) {
targetEntity.setMobile(mobile);
return this;
}
public Integer type() {
return targetEntity.getType();
}
public Builder type(Integer type) {
targetEntity.setType(type);
return this;
}
public Integer status() {
return targetEntity.getStatus();
}
public Builder status(Integer status) {
targetEntity.setStatus(status);
return this;
}
public Long createTime() {
return targetEntity.getCreateTime();
}
public Builder createTime(Long createTime) {
targetEntity.setCreateTime(createTime);
return this;
}
public Long lastModifyTime() {
return targetEntity.getLastModifyTime();
}
public Builder lastModifyTime(Long lastModifyTime) {
targetEntity.setLastModifyTime(lastModifyTime);
return this;
}
}
public interface FIELDS {
String ID = "id";
String USERNAME = "username";
String NICKNAME = "nickname";
String GENDER = "gender";
String AGE = "age";
String AVATAR_URL = "avatar_url";
String PASSWORD = "password";
String EMAIL = "email";
String MOBILE = "mobile";
String TYPE = "type";
String STATUS = "status";
String CREATE_TIME = "create_time";
String LAST_MODIFY_TIME = "last_modify_time";
}
public static final String TABLE_NAME = "user";
public static FieldConditionBuilder conditionBuilder() {
return new FieldConditionBuilder();
}
public static FieldConditionBuilder conditionBuilder(String prefix) {
return new FieldConditionBuilder(prefix);
}
public static FieldConditionBuilder conditionBuilder(Query<?> query) {
return conditionBuilder(query, null);
}
public static FieldConditionBuilder conditionBuilder(Query<?> query, String prefix) {
return new FieldConditionBuilder(query.owner(), query.dataSourceName(), prefix);
}
public static FieldConditionBuilder conditionBuilder(UserEntity entity) {
return conditionBuilder(entity, null);
}
public static FieldConditionBuilder conditionBuilder(UserEntity entity, String prefix) {
return new FieldConditionBuilder(entity.doGetSafeOwner(), entity.getDataSourceName(), prefix);
}
public static FieldConditionBuilder conditionBuilder(IDatabase owner, String prefix) {
return new FieldConditionBuilder(owner, prefix);
}
public static FieldConditionBuilder conditionBuilder(IDatabase owner, String dataSourceName, String prefix) {
return new FieldConditionBuilder(owner, dataSourceName, prefix);
}
public static class FieldConditionBuilder extends AbstractFieldConditionBuilder {
public FieldConditionBuilder() {
super(null, null, null);
}
public FieldConditionBuilder(String prefix) {
super(null, null, prefix);
}
public FieldConditionBuilder(Query<?> query) {
super(query.owner(), null, null);
}
public FieldConditionBuilder(Query<?> query, String prefix) {
super(query.owner(), query.dataSourceName(), prefix);
}
public FieldConditionBuilder(IDatabase owner) {
super(owner, null, null);
}
public FieldConditionBuilder(IDatabase owner, String prefix) {
super(owner, null, prefix);
}
public FieldConditionBuilder(IDatabase owner, String dataSourceName, String prefix) {
super(owner, dataSourceName, prefix);
}
public FieldCondition id() {
return createFieldCondition(UserEntity.FIELDS.ID);
}
public FieldCondition username() {
return createFieldCondition(UserEntity.FIELDS.USERNAME);
}
public FieldCondition nickname() {
return createFieldCondition(UserEntity.FIELDS.NICKNAME);
}
public FieldCondition gender() {
return createFieldCondition(UserEntity.FIELDS.GENDER);
}
public FieldCondition age() {
return createFieldCondition(UserEntity.FIELDS.AGE);
}
public FieldCondition avatarUrl() {
return createFieldCondition(UserEntity.FIELDS.AVATAR_URL);
}
public FieldCondition password() {
return createFieldCondition(UserEntity.FIELDS.PASSWORD);
}
public FieldCondition email() {
return createFieldCondition(UserEntity.FIELDS.EMAIL);
}
public FieldCondition mobile() {
return createFieldCondition(UserEntity.FIELDS.MOBILE);
}
public FieldCondition type() {
return createFieldCondition(UserEntity.FIELDS.TYPE);
}
public FieldCondition status() {
return createFieldCondition(UserEntity.FIELDS.STATUS);
}
public FieldCondition createTime() {
return createFieldCondition(UserEntity.FIELDS.CREATE_TIME);
}
public FieldCondition lastModifyTime() {
return createFieldCondition(UserEntity.FIELDS.LAST_MODIFY_TIME);
}
}
}
数据实体注解
@Entity
声明一个类为数据实体对象。
配置项 | 描述 |
---|---|
value | 实体名称(数据库表名称),默认采用当前类名称 |
@Id
声明一个类成员为主键,与 @Property
注解配合使用,无参数。
@Property
声明一个类成员为数据实体属性。
配置项 | 描述 |
---|---|
name | 实现属性名称,默认采用当前成员名称 |
autoincrement | 是否为自动增长,默认为 false |
sequenceName | 序列名称,适用于类似 Oracle 等数据库,配合 autoincrement 参数一同使用 |
useKeyGenerator | 指定键值生成器名称,默认为空表示不启用(仅当非自动增长且主键值为空时调用)。 目前框架提供了 IKeyGenerator.UUID 键值生成器,其采用 UUID 策略。可通过实现 IKeyGenerator 接口自行实现并通过 SPI 方式向框架注册。 |
nullable | 允许为空,默认为 true |
unsigned | 是否为无符号,默认为 false |
length | 数据长度,默认为 0 表示不限制 |
decimals | 小数位数,默认为 0 表示无小数 |
type | 数据类型,默认为 Type.FIELD.UNKNOWN |
示例: 使用自定义主键生成器 custom
为实体类的 id
属性自动赋值。
步骤1: 编写自定义主键生成器,为其命名为 custom
。
package net.ymate.platform.examples.persistence.jdbc;
import net.ymate.platform.core.persistence.IKeyGenerator;
import net.ymate.platform.core.persistence.IPersistence;
import net.ymate.platform.core.persistence.base.IEntity;
import net.ymate.platform.core.persistence.base.PropertyMeta;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
@KeyGenerator(value = "custom")
public class CustomKeyGenerator implements IKeyGenerator {
@Override
public Object generate(IPersistence<?, ?, ?, ?> owner, PropertyMeta propertyMeta, IEntity<?> entity) {
// 判断当前主键属性类型,仅对字符串类型生成
if (propertyMeta.getField().getType().equals(String.class)) {
if (entity instanceof UserEntity) {
String username = ((UserEntity) entity).getUsername();
String mobile = ((UserEntity) entity).getMobile();
if (StringUtils.isNotBlank(username) && StringUtils.isNotBlank(mobile)) {
// 将用户密和密码值联合进行MD5运算
return DigestUtils.md5Hex(username + mobile);
}
throw new IllegalArgumentException("username and mobile can not be empty.");
}
}
return null;
}
}
步骤2: 注册自定义主键生成器
方式一:通过在 META-INF/service/
或META-INF/service/internal/
目录下对应的 SPI
配置文件中增加由 步骤1 创建的自定义主键生成器类名。配置文件名称应为 net.ymate.platform.core.persistence.IKeyGenerator
,若不存在请手动创建并追加如下内容:
net.ymate.platform.examples.persistence.jdbc.CustomKeyGenerator
方式二:手动向主键生成器管理器进行注册。
IKeyGenerator.Manager.registerKeyGenerator("custom", CustomKeyGenerator.class);
步骤3: 调整数据实体类的 id
属性 @Property
注解配置:useKeyGenerator = "custom"
@Entity(UserEntity.TABLE_NAME)
@Comment("用户信息")
public class UserEntity extends BaseEntity<UserEntity, String> {
private static final long serialVersionUID = 1L;
@Id
@Property(name = FIELDS.ID, nullable = false, length = 32, useKeyGenerator = "custom")
@Comment("用户唯一标识")
@PropertyState(propertyName = FIELDS.ID)
private String id;
......
}
步骤4: 测试
下面的代码中包含了如何通过捕获 JDBC 模块初始化事件向管理器注册自定义主键生成器,如果与 SPI
方式同时使用会造成重复注册动作,尽管管理器会忽略重复注册的行为,但仍然建议避免重复操作,两种注册方式达到的目的是一样的,但更推荐使用 SPI
方式。
@EnableAutoScan
@EnableBeanProxy
public class Starter implements IApplicationInitializer {
static {
System.setProperty(IApplication.SYSTEM_MAIN_CLASS, Starter.class.getName());
}
private static final Log LOG = LogFactory.getLog(Starter.class);
@Override
public void afterEventInit(IApplication application, Events events) {
events.registerListener(ModuleEvent.class, new IEventListener<ModuleEvent>() {
@Override
public boolean handle(ModuleEvent context) {
if (Objects.equals(JDBC.MODULE_NAME, context.getSource().getName())
&& context.getEventName() == ModuleEvent.EVENT.MODULE_INITIALIZED) {
try {
IKeyGenerator.Manager.registerKeyGenerator("custom", CustomKeyGenerator.class);
} catch (Exception ignored) {
}
}
return false;
}
});
}
public static void main(String[] args) throws Exception {
try (IApplication application = YMP.run(args, new Starter())) {
UserEntity userEntity = UserEntity.builder()
.username("abc")
.mobile("13088888888")
.createTime(System.currentTimeMillis()).build();
LOG.info("Custom UserEntity Id: " + userEntity.save().getId());
}
}
}
@PK
声明一个类为某数据实体的复合主键对象,无参数。
示例:
@PK
public class UserExtPK {
@Property
private String uid;
@Property(name = "wx_id")
private String wxId;
// 省略Get/Set方法...
}
@Entity("user_ext")
public class UserExt {
@Id
private UserExtPK id;
@Property(name = "open_id", nullable = false, length = 32)
private String openId;
// 省略Get/Set方法...
}
@Readonly
声明一个成员为只读属性,数据实体更新时其值将被忽略,与 @Property
注解配合使用,无参数。
示例:
@Entity("demo")
public class Demo {
@Id
@Property
private String id;
@Property(name = "create_time")
@Readonly
private Date createTime;
// 省略Get/Set方法...
}
@Indexes
声明一组数据实体的索引。
配置项 | 描述 |
---|---|
value | 索引注解 @Index 集合 |
@Index
声明一个数据实体的索引。
配置项 | 描述 |
---|---|
name | 索引名称 |
unique | 是否唯一索引,默认为 true |
fields | 索引字段名称集合 |
示例:
@Indexes({
@Index(name="unique_uname", unique = true, fields = {UserEntity.FIELDS.USERNAME})
})
@Comment
实体或成员属性的注释内容。
@Default
为一个成员属性指定默认值。
配置项 | 描述 |
---|---|
value | 默认值 |
ignored | 是否忽略(即该默认值仅用于生成 DDL 语句,主要是为了避免函数名称导致 SQL 执行错误),默认为 false |
自动生成实体类
YMP 框架自 v1.x
开始就支持通过数据库表结构自动生成实体类代码,所以 v2.x
版本不但重构了实体代码生成器,而且更简单好用!
**步骤1:**配置数据实体代码生成器所需参数:
#-------------------------------------
# JDBC数据实体代码生成器配置参数
#-------------------------------------
# 是否生成新的BaseEntity类, 默认值: false(即表示使用框架提供的BaseEntity类)
ymp.params.jdbc.use_base_entity=
# 是否使用类名后缀, 不使用和使用的区别如: User->UserModel, 默认值: false
ymp.params.jdbc.use_class_suffix=true
# 实体类名后缀, 默认值: model
ymp.params.jdbc.class_suffix=entity
# 是否采用链式调用模式, 默认值: false
ymp.params.jdbc.use_chain_mode=true
# 为兼容历史数据库保持原表和字段名称的大小写,默认值: false
ymp.params.jdbc.keep_case=
# 自定义表或字段名称过滤器, 默认值: 空
ymp.params.jdbc.named_filter_class=
# 是否添加类成员属性值状态变化注解, 默认值: false
ymp.params.jdbc.use_state_support=true
# 数据库名称, 默认值: 空
ymp.params.jdbc.db_name=mydb
# 数据库用户名称, 默认值: 空
ymp.params.jdbc.db_username=root
# 数据库表名称前缀, 多个用'|'分隔, 默认值: 空
ymp.params.jdbc.table_prefix=ym_
# 否剔除生成的实体映射表名前缀, 默认值: false
ymp.params.jdbc.remove_table_prefix=true
# 预生成实体的数据表名称列表, 多个用'|'分隔, 默认值: 空(即全部生成)
ymp.params.jdbc.table_list=
# 排除的数据表名称列表, 在此列表内的数据表将不被生成实体, 多个用'|'分隔, 默认值: 空
ymp.params.jdbc.table_exclude_list=
# 需要添加@Readonly注解声明的字段名称列表, 多个用'|'分隔, 默认值: 空
ymp.params.jdbc.readonly_field_list=create_time
# 生成的代码文件输出路径, 默认值: ${root}/src/main/java
ymp.params.jdbc.output_path=
# 生成的代码所属包名称, 默认值: packages
ymp.params.jdbc.package_name=
实际上你可以什么都不用配置(请参看以上配置项说明,根据实际情况进行调整),但使用过程中需要注意以下几点:
-
在多数据源模式下,需要指定具体数据源名称,否则代码生成器使用的是默认数据源;
-
如果使用的 JDBC 驱动是
mysql-connector-java-6.x
及以上版本时,则必须配置db_name
和db_username
参数; -
实体及属性命名过滤器参数
named_filter_class
指定的类需要实现INamedFilter
接口;插件已提供了中文转拼音的过滤器接口实现类:
net.ymate.maven.plugins.support.ChinesePinyinNamedFilter
**步骤2:**添加插件配置,数据实体生成器是以 Maven 插件的形式提供的,需要在工程的 pom.xml
文件添加如下内容:
<plugin>
<groupId>net.ymate.maven.plugins</groupId>
<artifactId>ymate-maven-plugin</artifactId>
<version>1.0.2</version>
</plugin>
插件中默认已经包含 mysql-connector-java-8.0.32
驱动,若需要其它版本或其它类型数据库驱动时,需要在插件中配置相关依赖,如:
<plugin>
<groupId>net.ymate.maven.plugins</groupId>
<artifactId>ymate-maven-plugin</artifactId>
<version>1.0.2</version>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version>
</dependency>
<dependency>
<groupId>com.oracle.database.jdbc</groupId>
<artifactId>ojdbc8</artifactId>
<version>21.7.0.0</version>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>11.2.0.jre8</version>
</dependency>
</dependencies>
</plugin>
**步骤3:**在工程根路径下执行插件命令:
mvn ymate:entity -Doverwrite=true
插件命令参数说明:
参数 | 描述 |
---|---|
dev | 是否使用开发模式,默认为 false |
overwrite | 是否覆盖已存在的文件,默认为 false |
cfgFile | 加载指定的框架初始化配置文件,默认为空 |
dataSource | 指定数据源名称,默认为 default |
view | 是否为视图,默认为 false |
showOnly | 是否仅在控制台输出结构信息(不生成任何文件),默认为 false |
format | 控制台输出格式,配合 showOnly 使用,可选值:table markdown csv ,默认为 table |
beanOnly | 是否仅生成 JavaBean(非实体类),默认为 false |
apidocs | 是否使用 @ApiProperty 文档注解,配合 beanOnly 使用,默认为 false |
通过插件生成的代码默认放置在 src/main/java
路径,当数据库表发生变化时,重新执行插件命令就可以快速更新数据实体对象,是不是很更方便呢,大家可以动手尝试一下!:p
数据实体操作
本小节中使用的数据实体类 是指通过实体生成工具自动生成并继承了框架提供的 BaseEntity
抽象类。
针对非工具自动生成或并未继承 BaseEntity
的实体类,通过 EntityWrapper
类包装也可以达到同样的效果:
UserEntity user = new UserEntity();
user.setId(UUIDUtils.UUID());
user.setUsername("suninformation");
// ......
EntityWrapper<UserEntity> wrapper = EntityWrapper.bind(user);
wrapper.saveOrUpdate();
插入(Insert)
UserEntity user = UserEntity.builder()
.id(UUIDUtils.UUID())
.username("suninformation")
.nickname("有理想的鱼")
.password(DigestUtils.md5Hex("123456"))
.email("suninformation@163.com")
.build();
// 执行数据插入
user.save();
// 或者在插入时也可以指定/排除某些字段
user.save(Fields.create(UserEntity.FIELDS.NICKNAME, UserEntity.FIELDS.EMAIL).excluded(true));
// 或者插入前判断记录是否已存在,若已存在则执行记录更新操作
user.saveOrUpdate();
// 或者插入前判断记录是否已存在,若已存在则执行记录更新操作时仅更新指定的字段
user.saveOrUpdate(Fields.create(UserEntity.FIELDS.NICKNAME, UserEntity.FIELDS.EMAIL));
更新(Update)
方式一:常规更新
UserEntity user = UserEntity.builder()
.id("bc19f5645aa9438089c5e9954e5f1ac5")
.password(DigestUtils.md5Hex("654321"))
.gender("F")
.build();
// 执行记录更新
user.update();
// 或者仅更新指定的字段/排除某些字段
user.update(Fields.create(UserEntity.FIELDS.PASSWORD));
方式二:仅更新值发生变化的字段
// 从数据库中加载记录
UserEntity user = UserEntity.builder()
.id("bc19f5645aa9438089c5e9954e5f1ac5").build().load();
// 获取记录类成员属性状态包装器
EntityStateWrapper<UserEntity> stateWrapper = user.stateWrapper();
// 为字段赋值
stateWrapper.getEntity().bind()
.password(DigestUtils.md5Hex("654321"))
.gender("M");
// 执行更新(将排除掉值未发生变化的字段)
stateWrapper.update();
查询(Find)
方式一:根据记录ID加载
UserEntity user = UserEntity.builder()
.id("bc19f5645aa9438089c5e9954e5f1ac5")
.build();
// 根据记录ID加载全部字段
user = user.load();
// 或者根据记录ID加载指定的字段
user = user.load(Fields.create(UserEntity.FIELDS.USERNAME, UserEntity.FIELDS.NICKNAME));
方式二:根据实体属性设置条件
UserEntity user = UserEntity.builder()
.username("suninformation")
.email("suninformation@163.com")
.build();
// 非空属性之间将使用and条件连接,查询所有符合条件的记录并返回所有字段
IResultSet<UserEntity> users = user.find();
// 或者返回指定的字段
users = user.find(Fields.create(UserEntity.FIELDS.ID, UserEntity.FIELDS.PASSWORD));
// 非空属性之间将使用or条件连接,查询所有符合条件的记录并返回
users = user.matchAny().find();