MyBatis轻松接入金仓数据库:从零搭建到生产实践
接手国产化改造任务
去年做一个政务系统的国产化替换,客户明确要求数据库必须换成金仓。团队里没人用过,心里都有点打鼓。
我当时先用Spring Boot + JdbcTemplate跑了个demo,发现挺顺利。后来好奇MyBatis能不能也这么丝滑地接进去,就专门搭了个项目验证。结果从配依赖到跑通CRUD,一个小时不到全搞定。今天就把这次实战经历整理出来。
一、准备工作
配置文件里需要填连接信息,先找DBA要到这四项:
- 数据库IP和端口(默认54321)
- 数据库名
- 用户名(默认system)
- 密码
顺便用命令行验证一下能不能连上:
./ksql -U system -d test -h 192.168.1.100 -p 54321
连上了再继续下一步。
二、项目搭建
2.1 目录结构
mybatis-kingbase/
├── pom.xml
├── src/main/
│ ├── java/com/kingbase/
│ │ ├── mapper/
│ │ │ ├── IUserMapper.java # XML方式
│ │ │ └── IAppMapper.java # 注解方式
│ │ └── pojo/
│ │ ├── User.java
│ │ └── App.java
│ └── resources/
│ ├── jdbc.properties
│ ├── mybatis-config.xml
│ └── IUserMapper.xml
└── src/test/java/
└── TestMybatis.java
用了两种Mapper方式对比:IUserMapper是XML配置,IAppMapper是纯注解,方便看看哪种更适合自己的项目。
2.2 pom.xml依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.kingbase.example</groupId>
<artifactId>java-kingbase-mybatis</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<!-- 金仓JDBC驱动 -->
<dependency>
<groupId>cn.com.kingbase</groupId>
<artifactId>kingbase8</artifactId>
<version>9.0.0</version>
</dependency>
<!-- MyBatis核心 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.9</version>
</dependency>
<!-- 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.0</version>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>
关键点:金仓驱动groupId是cn.com.kingbase,我刚用时写成com.kingbase8一直拉不下来。
2.3 配置文件
jdbc.properties:
jdbc.driver=com.kingbase8.Driver
jdbc.url=jdbc:kingbase8://192.168.1.100:54321/test?useServerPrepStmts=true&rewriteBatchedStatements=true
jdbc.username=system
jdbc.password=123456
URL后面加了两个参数:useServerPrepStmts开启预编译、rewriteBatchedStatements优化批量插入,正式环境建议加上。
mybatis-config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="jdbc.properties"/>
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="kingbase8"/>
<property name="reasonable" value="false"/>
</plugin>
</plugins>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="IUserMapper.xml"/>
<mapper class="com.kingbase.mapper.IAppMapper"/>
</mappers>
</configuration>
两个地方需要注意:mapUnderscoreToCamelCase开启后,数据库字段user_name能自动映射到Java的userName;分页插件dialect要写成kingbase8才能生成正确分页SQL。
三、代码实现
3.1 实体类
User.java和App.java代码一样:
package com.kingbase.pojo;
public class User {
private Long id;
private String name;
public User() {}
public User(Long id, String name) {
this.id = id;
this.name = name;
}
// getter/setter省略...
@Override
public String toString() {
return "User{id=" + id + ", name='" + name + "'}";
}
}
3.2 XML方式:IUserMapper
接口:
package com.kingbase.mapper;
import com.kingbase.pojo.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface IUserMapper {
Integer insertUser(User user);
Integer deleteUser(Long id);
Integer updateUser(User user);
List<User> selectUsers();
List<User> selectUserByPage(@Param("pageNum") Integer pageNum,
@Param("pageSize") Integer pageSize);
}
XML映射文件:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kingbase.mapper.IUserMapper">
<insert id="insertUser" parameterType="com.kingbase.pojo.User">
INSERT INTO test_user (id, name) VALUES (#{id}, #{name})
</insert>
<delete id="deleteUser" parameterType="long">
DELETE FROM test_user WHERE id = #{id}
</delete>
<update id="updateUser" parameterType="com.kingbase.pojo.User">
UPDATE test_user SET name = #{name} WHERE id = #{id}
</update>
<select id="selectUsers" resultType="com.kingbase.pojo.User">
SELECT id, name FROM test_user
</select>
<select id="selectUserByPage" resultType="com.kingbase.pojo.User">
SELECT id, name FROM test_user
LIMIT #{pageNum}, #{pageSize}
</select>
</mapper>
用LIMIT做分页,代码比Oracle的rownum简洁多了。
3.3 注解方式:IAppMapper
package com.kingbase.mapper;
import com.kingbase.pojo.App;
import org.apache.ibatis.annotations.*;
import java.util.List;
public interface IAppMapper {
@Insert("insert into test_app(id,name) values(#{id},#{name})")
Integer insertApp(App app);
@Delete("delete from test_app where id = #{id}")
Integer deleteApp(Long id);
@Update("update test_app set name = #{name} where id = #{id}")
Integer updateApp(App app);
@Select("select * from test_app")
@Results({
@Result(id = true, column = "id", property = "id"),
@Result(column = "name", property = "name")
})
List<App> selectApps();
}
简单SQL用注解方便,复杂查询还是XML更清晰,看个人习惯。
四、测试验证
测试类覆盖了XML和注解两种Mapper、以及分页功能:
import com.kingbase.mapper.IAppMapper;
import com.kingbase.mapper.IUserMapper;
import com.kingbase.pojo.App;
import com.kingbase.pojo.User;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import java.io.IOException;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
public class TestMybatis {
@Test
public void testUserMapper() throws IOException, SQLException {
SqlSessionFactory factory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = factory.openSession();
Statement stmt = sqlSession.getConnection().createStatement();
// 建表
try {
stmt.execute("drop table if exists test_user");
} catch (SQLException e) {}
stmt.execute("create table test_user(id number(20) primary key, name varchar(100))");
IUserMapper mapper = sqlSession.getMapper(IUserMapper.class);
// 插入10条
for (int i = 1; i <= 10; i++) {
mapper.insertUser(new User((long) i, "insert"));
}
// 删除id=1
mapper.deleteUser(1L);
// 更新id=2
mapper.updateUser(new User(2L, "update"));
// 查询所有
List<User> userList = mapper.selectUsers();
userList.forEach(System.out::println);
// 分页查询
List<User> usersByPage = mapper.selectUserByPage(0, 3);
System.out.println("分页结果: " + usersByPage);
stmt.close();
sqlSession.commit();
sqlSession.close();
}
@Test
public void testAppMapper() throws IOException, SQLException {
SqlSessionFactory factory = new SqlSessionFactoryBuilder()
.build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = factory.openSession();
Statement stmt = sqlSession.getConnection().createStatement();
// 建表
try {
stmt.execute("drop table if exists test_app");
} catch (SQLException e) {}
stmt.execute("create table test_app(id number(20) primary key, name varchar(100))");
IAppMapper mapper = sqlSession.getMapper(IAppMapper.class);
// 插入10条
for (int i = 1; i <= 10; i++) {
mapper.insertApp(new App((long) i, "insert" + i));
}
// 删除id=1
mapper.deleteApp(1L);
// 更新id=2
mapper.updateApp(new App(2L, "update"));
// 分页测试
PageHelper.startPage(2, 3);
List<App> appList = mapper.selectApps();
PageInfo<App> pageInfo = new PageInfo<>(appList);
System.out.println("分页后: " + pageInfo.getList());
sqlSession.commit();
sqlSession.close();
}
}
运行结果:
User{id=2, name='update'}
User{id=3, name='insert'}
...
分页后: [App{id=6, name='insert6'}, App{id=7, name='insert7'}, App{id=8, name='insert8'}]
从输出能看出:建表成功、数据正确、分页按预期工作。
五、两种Mapper方式对比
| 维度 | XML配置 | 注解 |
|---|---|---|
| SQL与代码 | 分离 | 混在一起 |
| 复杂SQL | 清晰 | 字符串拼接 |
| 动态SQL | 天然支持 | 需要用Provider |
| 调试 | 有语法检查 | 编译期不检查 |
| 适用场景 | 项目工程 | 简单demo |
建议:简单CRUD用注解省事,复杂业务查询用XML更好维护。
六、生产环境踩坑记录
驱动类名:com.kingbase8.Driver,不是com.kingbase.Driver。
分页方言:PageHelper的helperDialect要写成kingbase8,写成mysql语法不对,写成postgresql也能用,但不如kingbase8稳定。
KingbaseMode参数:金仓有Oracle和MySQL两种兼容模式,连接URL加上KingbaseMode=mysql可以用MySQL方言的LIMIT,不加就用Oracle的rownum。示例用的是默认模式,分页用了LIMIT说明数据库用MySQL模式创建的。
批量操作优化:连接URL加上&rewriteBatchedStatements=true,批量插入性能能提升不少。
大小写问题:表名和字段名建议全部小写,用双引号会导致大小写敏感,各种莫名奇妙的麻烦。
七、总结
从MySQL切到金仓,MyBatis项目改的地方很少:pom.xml换驱动、配置改URL、分页方言改成kingbase8。如果SQL里没用数据库特有语法,基本无缝迁移。
我的建议是:先跑通demo验证可行性,再考虑分库分表、读写分离这些高级特性。金仓对MyBatis的兼容性整体做得不错,踩坑的概率比你想象的低。