问题
我们使用roaringbitmap都知道,这个是一个扩展安装到PG里面的,你安装后新建字段的时候可以选择一个叫***roaringbitmap***的数据类型,但是这个类型java里面是没有的,也就是说你查出来mybatis不能直接映射,数据库里面的值长这样:\x3a3000000100000000000200100000008a278b278f27。那么你如果想直接读,可以自己通过位图函数rb_to_array()查出来的时候转成数组然后返回给java。
select rb_to_array(bitmap) from table where id=xxxx
这样可以,但是咱都用mybatisplus了,本来就是图省事,可以少写sql,内置的getById方法或者save方法这些都用不了就很难受。
解决问题
为了能继续偷懒,那肯定是要自己去实现roaringbitmap的RoaringBitmapTypeHandler。
相关依赖
<dependency>
<groupId>org.roaringbitmap</groupId>
<artifactId>RoaringBitmap</artifactId>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.1</version>
</dependency>
注意如果你maven里面用了dependencyManagement,spring-boot-dependencies里面自己是带了postgresql的,自己检查看一下版本对不对,不对的手动排除或者升级你的spring-boot-dependencies
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
<exclusions>
<exclusion>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</dependencyManagement>
实体类样例
import org.roaringbitmap.RoaringBitmap;
@TableName("public.entity")
public class Entity implements Serializable {
private Long id;
@TableField(typeHandler = RoaringBitmapTypeHandler.class)
private RoaringBitmap userbits;
}
这里的RoaringBitmap就是刚才引入的依赖里面的。
自定义类型处理器
mybatis的自定义类型处理器需要继承BaseTypeHandler
public class RoaringBitmapTypeHandler extends BaseTypeHandler<RoaringBitmap> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, RoaringBitmap roaringBitmap, JdbcType jdbcType) throws SQLException {
}
@Override
public RoaringBitmap getNullableResult(ResultSet rs, String columnName) throws SQLException {
}
@Override
public RoaringBitmap getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
}
@Override
public RoaringBitmap getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
}
}
咱先把类建好再看里面的方法,主要就是set和get对应就是你插入更新和查询的时候遇到这个类型的处理逻辑。
我们开头说过咱数据库里的数据长这样“\x3a30000000000000”,除去\x其实是一个16进制数。这边提供一种实现思路:
@Override
public RoaringBitmap getNullableResult(ResultSet rs, String columnName) throws SQLException {
// 从数据库中读取字符串
String roaringBitmapString = rs.getString(columnName);
roaringBitmapString = roaringBitmapString.replace("\\x", ""); // 去除 \x 前缀
byte[] byteArray = DatatypeConverter.parseHexBinary(roaringBitmapString);
RoaringBitmap roaringBitmap = new RoaringBitmap();
try {
roaringBitmap.deserialize(new DataInputStream(new ByteArrayInputStream(byteArray)));
} catch (IOException e) {
e.printStackTrace();
}
return roaringBitmap;
}
@Override
public RoaringBitmap getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String roaringBitmapString = rs.getString(columnIndex);
roaringBitmapString = roaringBitmapString.replace("\\x", ""); // 去除 \x 前缀
return RoaringBitmap.bitmapOf(Integer.parseInt(roaringBitmapString, 16));
}
这样我们读取roaringbitmap类型的处理就好了
划重点
刚才我们直接通过rs.getString获取到了值没毛病,按照刚才的思路,我set值的时候,也先给他转成16进制字符串再set应该也没毛病吧。
@Override
public void setNonNullParameter(PreparedStatement ps, int i, RoaringBitmap roaringBitmap, JdbcType jdbcType) throws SQLException {
// convertRoaringToHex(roaringBitmap)等于是\x3a30000000000000这样的一个字符串
ps.setString(i, convertRoaringToHex(roaringBitmap));
}
如果你这么写了,恭喜你,你会看到这样的结果
Caused by: org.postgresql.util.PSQLException: ERROR: column "userbits" is of type roaringbitmap but expression is of type character varying
你是不可以直接把一个string塞到roaringbitmap类型里面的setNonNullParameter方法得通过setObject设置值,注意这里得PGobject老版本的依赖里面是没有的。
@Override
public void setNonNullParameter(PreparedStatement ps, int i, RoaringBitmap roaringBitmap, JdbcType jdbcType) throws SQLException {
PGobject object = new PGobject();
object.setType("roaringbitmap");
object.setValue(convertRoaringToHex(roaringBitmap));
ps.setObject(i, object);
}
private String convertRoaringToHex(RoaringBitmap rb) {
try {
// 将 RoaringBitmap 对象序列化为字节数组
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
rb.serialize(dos);
dos.close();
byte[] byteArray = bos.toByteArray();
// 将字节数组转换为16进制字符串
StringBuilder hex = new StringBuilder(byteArray.length * 2);
hex.append("\\x");
for (byte b : byteArray) {
hex.append(String.format("%02x", b & 0xFF));
}
return hex.toString();
} catch (IOException e) {
throw new RuntimeException("Failed to convert RoaringBitmap to hex string", e);
}
}
到这所有问题都解决了,注意你要使用RoaringBitmap的地方都加了这个typehandler,通过这个例子咱以后遇到其他PG特有的数据类型也可以同理写自定义处理器。
别忘了注册你的自定义处理器
mybatis-plus:
type-handlers-package: com.merkle.tagging_framework.config.handle
最后附上完整的处理器代码:
public class RoaringBitmapTypeHandler extends BaseTypeHandler<RoaringBitmap> {
@Override
public void setNonNullParameter(PreparedStatement ps, int i, RoaringBitmap roaringBitmap, JdbcType jdbcType) throws SQLException {
PGobject object = new PGobject();
object.setType("roaringbitmap");
object.setValue(convertRoaringToHex(roaringBitmap));
ps.setObject(i, object);
}
private String convertRoaringToHex(RoaringBitmap rb) {
try {
// 将 RoaringBitmap 对象序列化为字节数组
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(bos);
rb.serialize(dos);
dos.close();
byte[] byteArray = bos.toByteArray();
// 将字节数组转换为16进制字符串
StringBuilder hex = new StringBuilder(byteArray.length * 2);
hex.append("\\x");
for (byte b : byteArray) {
hex.append(String.format("%02x", b & 0xFF));
}
return hex.toString();
} catch (IOException e) {
throw new RuntimeException("Failed to convert RoaringBitmap to hex string", e);
}
}
@Override
public RoaringBitmap getNullableResult(ResultSet rs, String columnName) throws SQLException {
String roaringBitmapString = rs.getString(columnName);
roaringBitmapString = roaringBitmapString.replace("\\x", "");
byte[] byteArray = DatatypeConverter.parseHexBinary(roaringBitmapString);
RoaringBitmap roaringBitmap = new RoaringBitmap();
try {
roaringBitmap.deserialize(new DataInputStream(new ByteArrayInputStream(byteArray)));
} catch (IOException e) {
e.printStackTrace();
}
return roaringBitmap;
}
@Override
public RoaringBitmap getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
String roaringBitmapString = rs.getString(columnIndex);
roaringBitmapString = roaringBitmapString.replace("\\x", "");
byte[] byteArray = DatatypeConverter.parseHexBinary(roaringBitmapString);
RoaringBitmap roaringBitmap = new RoaringBitmap();
try {
roaringBitmap.deserialize(new DataInputStream(new ByteArrayInputStream(byteArray)));
} catch (IOException e) {
e.printStackTrace();
}
return roaringBitmap;
}
@Override
public RoaringBitmap getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
String roaringBitmapString = cs.getString(columnIndex);
roaringBitmapString = roaringBitmapString.replace("\\x", ""); // 去除 \x 前缀
byte[] byteArray = DatatypeConverter.parseHexBinary(roaringBitmapString);
RoaringBitmap roaringBitmap = new RoaringBitmap();
try {
roaringBitmap.deserialize(new DataInputStream(new ByteArrayInputStream(byteArray)));
} catch (IOException e) {
e.printStackTrace();
}
return roaringBitmap;
}
}