MyBatis轻松接入金仓数据库:从零搭建到生产实践

0 阅读5分钟

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的兼容性整体做得不错,踩坑的概率比你想象的低。