1.前言
在springboot项目开发中,一般使用关系型数据库作为主库存储数据,有时候业务场景需要在既有的表结构上,扩展自定义业务信息. 这种场景下一般使用json类型存储。本文总结springboot项目中,借助mybatis-plus操作json实践方案
2.方案分析
2.1 为什么是json
JSON类型相对于传统的关系型结构,其具有数据本身对结构描述、动态扩展和嵌套等特性,能够更加自由地表示和存储数据
2.2 数据库的选择
json字段的存储依赖于底层选择的数据库, 有的关系型数据库已经支持json,比如mysql5.7版本中,引入了JSON类型。如果没有特殊的json类型, 我们可以使用text类型存储json文本。因此要分两种情况分析. 这两种模式区别:
- 提供json类型数据库,在查询灵活程度上更高,比如可以针对json指定key的value进行查询。text之恶能作为普通文本匹配
- 提供json类型数据库,查询会部分依赖底层特殊查询语法. text则是通用的数据类型不存在该情况。
3. 实战
无论底层数据库使用text类型还是json类型。持久层使用mybatis-plus都要处理json与对象的映射问题。创建一个A***ount账号对象为例,增加一个extendJson作为存储扩展数据的json对象
@TableName(value = "a***ount", autoResultMap = true)
public class A***ount {
@TableId(type = IdType.AUTO)
private Long id;
private String name;
private String username;
/**
* 注意!! 必须开启映射注解
*
* @TableName(autoResultMap = true)
* <p>
* 以下两种类型处理器,二选一 也可以同时存在
* <p>
* 注意!!选择对应的 JSON 处理器也必须存在对应 JSON 解析依赖包
*/
//@TableField(typeHandler = JacksonTypeHandler.class)
@TableField(typeHandler = FastjsonTypeHandler.class)
private JSONObject extendJson;
//setter/getter忽略
以上部分主要参考mp官网:https://baomidou.***/ >>字段类型处理器
3.1 使用text字段(h2数据库)
使用text字段测试json字段我们使用h2数据库进行测试
- h2版本: 1.4.200(该版本不支持原生的json字段)
3.1.1 建表语句
使用liquibase管理建表语句
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<changeSet author="demo" id="a***ount.createTable">
<createTable tableName="a***ount" remarks="账号表">
<!--设置id自增 起始位置从10000 每次加1-->
<column name="id" remarks="账户ID" type="bigint" autoIncrement="true" incrementBy="1" startWith="10000">
<constraints primaryKey="true" nullable="false"/>
</column>
<!--用户名增加唯一索引-->
<column name="username" remarks="用户名" type="VARCHAR(32)">
<constraints nullable="false" unique="true" uniqueConstraintName="uniq_username"/>
</column>
<column name="password" remarks="密码" type="VARCHAR(32)"/>
<column name="name" remarks="姓名" type="VARCHAR(20)"/>
<column name="sex" remarks="性别" type="CHAR(1)"/>
<column name="phone" remarks="手机" type="VARCHAR(100)"/>
<column name="email" remarks="邮件" type="VARCHAR(100)"/>
<column name="create_time" remarks="创建时间" type="datetime(0)"/>
<column name="update_time" remarks="修改时间" type="datetime(0)"/>
<!-- <column name="extend_json" remarks="拓展字段使用" type="json"/>-->
<column name="extend_json" remarks="拓展字段使用" type="text"/>
</createTable>
</changeSet>
<!--loadData:加载 csv 文件到已存在的表中-->
<changeSet author="easy-log-demo" id="a***ount.loadData" >
<loadData tableName="a***ount" file="db/liquibase/csv/a***ount.csv" >
</loadData>
</changeSet>
</databaseChangeLog>
3.1.2 数据操作与查询
text存储json的数据操作与查询与普通text操作无差别
@Service
public class A***ountServiceImpl implements A***ountService {
@Autowired
private A***ountMapper a***ountMapper;
public void createA***ount(A***ount a***ount) {
a***ount.setUsername(UUID.randomUUID().toString().replace("-", ""));
this.a***ountMapper.insert(a***ount);
}
public A***ount updateA***ount(A***ount a***ount) {
this.a***ountMapper.updateById(a***ount);
return this.a***ountMapper.selectById(a***ount.getId());
}
@Override
public List<A***ount> listAll() {
return this.a***ountMapper.selectList(Wrappers.emptyWrapper());
}
}
效果:
3.2 使用json字段(mysql数据库)
3.2.1 建表语句
使用liquibase管理建表语句
- MySQL使用版本: 大于等于5.7
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<changeSet author="demo" id="a***ount.createTable">
<createTable tableName="a***ount" remarks="账号表">
<!--设置id自增 起始位置从10000 每次加1-->
<column name="id" remarks="账户ID" type="bigint" autoIncrement="true" incrementBy="1" startWith="10000">
<constraints primaryKey="true" nullable="false"/>
</column>
<!--用户名增加唯一索引-->
<column name="username" remarks="用户名" type="VARCHAR(32)">
<constraints nullable="false" unique="true" uniqueConstraintName="uniq_username"/>
</column>
<column name="password" remarks="密码" type="VARCHAR(32)"/>
<column name="name" remarks="姓名" type="VARCHAR(20)"/>
<column name="sex" remarks="性别" type="CHAR(1)"/>
<column name="phone" remarks="手机" type="VARCHAR(100)"/>
<column name="email" remarks="邮件" type="VARCHAR(100)"/>
<column name="create_time" remarks="创建时间" type="datetime(0)"/>
<column name="update_time" remarks="修改时间" type="datetime(0)"/>
<column name="extend_json" remarks="拓展字段使用" type="json"/>
</createTable>
</changeSet>
<!--loadData:加载 csv 文件到已存在的表中-->
<changeSet author="easy-log-demo" id="a***ount.loadData" >
<loadData tableName="a***ount" file="db/liquibase/csv/a***ount.csv" >
</loadData>
</changeSet>
</databaseChangeLog>
3.2.2 数据操作与查询
mysql支持json类型因此借助特定的语法可以实现json精确查询及模糊查询
@Service
public class A***ountServiceImpl implements A***ountService {
@Autowired
private A***ountMapper a***ountMapper;
public void createA***ount(A***ount a***ount) {
a***ount.setUsername(UUID.randomUUID().toString().replace("-", ""));
this.a***ountMapper.insert(a***ount);
}
public A***ount updateA***ount(A***ount a***ount) {
this.a***ountMapper.updateById(a***ount);
return this.a***ountMapper.selectById(a***ount.getId());
}
/**
* json 数据模糊查询
* @param key extend_json 中的json的key
* @param value extend_json 中的json的key对应value
* @return
*/
public List<A***ount> listByJsonLike(String key, String value) {
// QueryChainWrapper<A***ount> queryWrapper = new QueryChainWrapper<>(this.a***ountMapper);
LambdaQueryWrapper<A***ount> queryWrapper = Wrappers.<A***ount>lambdaQuery();
//json字段模式查询
queryWrapper.apply("JSON_EXTRACT(extend_json, '$." + key + "') LIKE {0}", "%" + value + "%")
.ge(A***ount::getId, 10000);
return this.a***ountMapper.selectList(queryWrapper);
}
/**
* json 数据精确查询
* @param key extend_json 中的json的key
* @param value extend_json 中的json的key对应value
* @return
*/
public List<A***ount> listByJsonEquals(String key, String value) {
LambdaQueryWrapper<A***ount> queryWrapper = Wrappers.<A***ount>lambdaQuery();
//json字段精确查询
queryWrapper.apply("JSON_EXTRACT(extend_json, '$." + key + "') = {0}", value);
return this.a***ountMapper.selectList(queryWrapper);
}
@Override
public List<A***ount> listAll() {
return this.a***ountMapper.selectList(Wrappers.emptyWrapper());
}
}
- 效果:测试json内部字段模糊查询
4. 附录
4.1 MySQL JSON索引用法
TODO MySQLJSON索引用法介绍
4.2 mybatis-plus json查询用法
public class YourService {
@Autowired
private YourMapper yourMapper;
public YourEntity getByJsonKey(String key, String value) {
QueryWrapper<YourEntity> queryWrapper = Wrappers.<YourEntity>lambdaQuery()
.apply("json_data->'$.key' = {0}", value);
return yourMapper.selectOne(queryWrapper);
}
}
在上述示例中,.apply(“json_data->‘$.key’ = {0}”, value) 中的 {0} 将会被 MyBatis-Plus 自动处理为预编译参数,保证了 SQL 的安全性。
请确保你的 MyBatis-Plus 版本支持 .apply() 方法,该方法可以用于执行自定义的 SQL 查询条件。
5. 参考文档
- mybatis-plus字段类型处理器
- mybatis-plus