☰
Spring Boot集成GBase 8s最小demo:驱动配置与事务回滚
2026/9/26 6:44:26 网站建设 项目流程

简介:这是一份面向Java开发者的Spring Boot集成GBase 8s数据库的入门示例项目,以MyBatis作为持久层框架,逐步演示了从添加依赖、配置数据库连接与数据源、创建Mapper接口到执行增删改查的完整集成过程,适合需要为生产系统适配国产数据库或学习Spring Boot数据访问的开发者参考。压缩包共33个文件,包括5个Java源码、8个XML映射文件、8个class编译结果、3个properties配置文件和GBase 8s的JDBC驱动jar,总大小24.37MB,目录按照src/main和target等标准Maven布局组织,便于对照源码与构建产物进行排查。目前已有1485人学习浏览,可用于快速验证Spring Boot与GBase 8s的兼容性。通过MyBatis逆向工程可以自动生成实体类、Mapper接口和XML映射配置,减少DAO层手工编码;示例还着重展示了GBase 8s与常见数据库在SQL语法上的差异、特定异常的处理方式以及调试排错思路,能够帮助开发者在实际项目中复用这套集成配置,规避驱动加载失败、连接超时等典型问题。

1. 这就是集成 GBase 8s 最该先跑通的小 demo

接手国产化数据库替换的老项目时,最怕的不是 Oracle 语法改造,而是应用侧连不上、驱动不认、连接池反复报错。Spring Boot 集成 GBase 8s 的小 demo,就是把这条最关键的链路先打通:Maven 依赖、数据源配置、SQL 执行、事务回滚,全部在一个最小工程里跑一遍。适合正在做信创适配、或者领导突然让你“调研一下 GBase 8s 能不能接”的 Java 工程师,花半小时验证可行性,比写十页调研报告有用。我最早做这个 demo 时,卡在驱动名称和 URL 格式上浪费了一晚上,这类坑后面会逐个拆开说。

2. 先说清楚 GBase 8s 的 JDBC 驱动和依赖怎么进工程

2.1 驱动这关:不是所有 GBase 驱动都一样

GBase 8s 是南大通用的数据库产品,底层脱胎于 Informix,所以它的 JDBC 驱动沿用了 Informix 那一套类名和 URL 风格。常见驱动类名是com.gbase.jdbc.Driver,但不同发布版本可能带后缀,比如com.gbase.jdbc.ForGBaseDriver。我第一次拿到驱动包时,从 jar 里的 META-INF/services 中找java.sql.Driver文件,才确认了正确的类名,这个方法比翻资料可靠。

驱动的获取通常不是从 Maven 中央仓库直接拉,因为 GBase 官方并不把驱动发布到公共仓库。常见做法是找数据库安装目录下的lib文件夹,里面会有类似gbase-connector-java-xxx.jar的文件;或者从官方技术支持渠道拿。拿到 jar 后有两种引入方式:

  • 直接放到项目lib目录,用 system scope 引入
  • 安装到本地 Maven 仓库,按普通依赖引用

我一般选择第二种,因为 system scope 在 Spring Boot 打包成 fat jar 时容易出问题,虽然可以用includeSystemScope配置规避,但终究不如本地仓库干净。

下面是安装到本地仓库的命令,用 Maven 插件执行:

mvn install:install-file \ -Dfile=/path/to/gbase-connector-java-8.3.0.jar \ -DgroupId=com.gbase \ -DartifactId=gbase-connector-java \ -Dversion=8.3.0 \ -Dpackaging=jar

执行完后在 pom.xml 里声明依赖:

<dependency> <groupId>com.gbase</groupId> <artifactId>gbase-connector-java</artifactId> <version>8.3.0</version> </dependency>

参数说明:-Dfile指向驱动 jar 的实际路径,-DgroupId、-DartifactId、-Dversion完全由你自定义,只要能和你 pom.xml 里对应上就行。不指定-Dversion时,Maven 会默认成1.0,导致 pom 里版本对不上,所以这里最好显式写清楚。

2.2 本地 lib 引入的方式和打包坑

有些团队不允许动开发机上的 Maven 本地仓库,或者驱动包拷过来就直接放项目里了,那就用 system scope。pom 配置如下:

<dependency> <groupId>com.gbase</groupId> <artifactId>gbase-connector-java</artifactId> <version>8.3.0</version> <scope>system</scope> <systemPath>${project.basedir}/lib/gbase-connector-java-8.3.0.jar</systemPath> </dependency>

注意,用 system scope 后,Spring Boot 的spring-boot-maven-plugin默认打包不会把该 jar 放进最终的可执行 jar 里。解决方法是给打包插件加配置:

<plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <includeSystemScope>true</includeSystemScope> </configuration> </plugin>

这个配置让 Spring Boot 打包时把 system scope 的依赖一起打进去,否则部署到生产环境时,运行java -jar会直接报ClassNotFoundException: com.gbase.jdbc.Driver。如果是用spring-boot-maven-plugin默认配置,且没有加这个参数,本地 IDEA 里跑得通、打成 jar 就崩,这类玄学问题基本都是这个原因。

验证驱动 jar 是否已被打包,可以用命令查:

jar tf target/demo-app.jar | grep gbase

能看到BOOT-INF/lib/gbase-connector-java-8.3.0.jar才说明驱动真的进包了。只看到BOOT-INF/classes下出现驱动类是没有的,那种情况十有八九是没打进去。

3. 配置数据源与连接池:URL 格式是第一个分水岭

3.1 application.yml 里的最小可用配置

GBase 8s 的 JDBC URL 风格接近 Informix,格式是jdbc:gbase://ip:port/dbname:informix.specific.attribute=value;,但具体版本可能略有差异。我用的一个稳定写法是:

spring: datasource: driver-class-name: com.gbase.jdbc.Driver url: jdbc:gbase://192.168.1.100:9088/testdb username: gbasedba password: gbasedba123 hikari: minimum-idle: 5 maximum-pool-size: 20 idle-timeout: 30000 connection-timeout: 30000 max-lifetime: 1800000

注意 URL 里没有写成jdbc:gbase://192.168.1.100:9088/testdb:informix.specific...这种带扩展参数的形式,因为最小 demo 不需要那些高级特性。GBase 8s 默认端口一般是9088,但如果你装的是 GBase 8s 的安全版本,端口可能不同,要以实际实例配置为准。driver-class-name这个值必须和驱动 jar 里的类完全一致,多一个包名都不行。

HikariCP 参数里connection-timeout设 30 秒比较保守,如果数据库在远端且网络延迟较高,这个值设太短会导致启动时频繁报连接超时。max-lifetime要小于数据库侧的会话超时时间,否则连接被数据库端回收后,连接池还傻傻地复用已失效的连接。

3.2 连接池选型:为什么默认 Hikari 就够了

Spring Boot 2.x 之后的默认连接池是 HikariCP,性能好、配置简单,GBase 8s 的驱动只要实现了标准 JDBC 接口,Hikari 就能兼容。我不建议在 demo 阶段换成 Druid 或者 DBCP,虽然 Druid 有监控页面很诱人,但那属于锦上添花,不是打通链路的前提。

如果一定要用 Druid,配置上反而要多注意一个坑:Druid 会主动检测连接有效性,默认的validation-query是SELECT 1,但 GBase 8s 对这条 SQL 的响应和 MySQL 没有区别,基本都能跑通。然而 Druid 的validation-query-timeout如果不配,高并发下可能因为连接池中的连接被数据库端提前断开而报Communications link failure之类的错。Demo 阶段不想排查连接池的疑难杂症,就老老实实用 Hikari,数据源配置写完就能跑。

另外,GBase 8s 驱动在获取连接时可能比较慢,特别是第一次连接,需要加载内部的一些库文件。如果你的连接池connection-timeout设得很短,比如只有 5 秒,调用DataSource.getConnection()就可能超时。这个现象在 Linux 服务器上比 Windows 上更明显,可能是字符集或系统库加载差异导致的。调大connection-timeout到 30 秒以上能缓解,但治本的方法是确认驱动版本是否和数据库小版本完全兼容。

3.3 没有数据库实例时,怎么用本地环境快速验证

做 demo 最大的现实障碍是:不一定有现成的 GBase 8s 数据库实例。常见的做法是在本地用 Docker 跑一个 GBase 8s 容器,但 GBase 8s 官方镜像并不像 MySQL 那样在 Docker Hub 上随便拉,通常需要通过官方渠道获取镜像包,然后docker load导入。

拿到镜像后,启动命令大致是:

docker run -d \ -p 9088:9088 \ -e GBASEDBUSER=gbasedba \ -e GBASEDBPASSWORD=gbasedba123 \ --name gbase8s \ gbase8s:v8.3

启动以后,用下方命令确认容器日志里是否出现实例就绪的标志,比如Server started successfully或oninit success:

docker logs -f gbase8s

注意:容器里默认的库名不一定是testdb,可能需要进容器用dbaccess创建一个库,或者连接系统库sysmaster来验证连通性。我习惯先用dbaccess sysmaster登录一次,确认数据库服务本身没问题,再跑去写 Spring Boot 配置,这样能避免把数据库的问题误判成代码问题。数据库都起不来的话,demo 做得再漂亮也是白搭。

4. 写一个能跑通的增删改查 demo:从建表到 Service

4.1 建表语句和对应的数据模型

GBase 8s 的 SQL 语法和 Oracle 有不少相似之处,但也有一些差异。拿自增主键来说,GBase 8s 用的是serial类型,不是 MySQL 的auto_increment,也不是 PostgreSQL 的serial(语法细节上还是有差的)。下面这条建表语句是一个最小可用的用户表示例:

create table t_user ( id serial not null, name varchar(64) not null, age integer, primary key (id) );

注意serial类型在 GBase 8s 里就是自增整数,不需要额外指定步长或起始值。如果你看到别人的建表语句里写serial(100),那表示起始值是 100。这在初始化测试数据时有用,但不建议在 demo 里加,因为显式插入带 id 的行容易和自增逻辑冲突。

对应的 Java 实体类:

public class User { private Integer id; private String name; private Integer age; // getter / setter 省略,实际开发用 Lombok 也行 }

从 Oracle 或 MySQL 迁过来的人要特别注意,GBase 8s 的字段类型varchar在 JDBC 里返回的是java.lang.String,integer返回的是java.lang.Integer,这块没有幺蛾子。真正容易踩的是表名和字段名的大小写问题,GBase 8s 默认会把未加引号的标识符转为小写,但表的存储和查询行为在大小写敏感配置下可能会不一致,后续避坑章节详细说。

4.2 Mapper 接口与 XML 的配合方式

我用的是 MyBatis,这是国内 Spring Boot 项目最常见的 ORM 框架。先写 Mapper 接口:

package com.example.demo.mapper; import com.example.demo.entity.User; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; @Mapper public interface UserMapper { User selectById(@Param("id") Integer id); int insertUser(User user); int updateUser(User user); int deleteById(@Param("id") Integer id); }

XML 文件写在哪里都行,只要在application.yml里指定mybatis.mapper-locations即可。推荐放在resources/mapper/UserMapper.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.example.demo.mapper.UserMapper"> <select id="selectById" resultType="com.example.demo.entity.User"> select id, name, age from t_user where id = #{id} </select> <insert id="insertUser" useGeneratedKeys="true" keyProperty="id"> insert into t_user (name, age) values (#{name}, #{age}) </insert> <update id="updateUser"> update t_user set name = #{name}, age = #{age} where id = #{id} </update> <delete id="deleteById"> delete from t_user where id = #{id} </delete> </mapper>

逻辑说明:selectById用resultType直接映射到实体类,列名id、name、age与实体属性一一对应,所以不需要 resultMap。insertUser里useGeneratedKeys="true"是让 MyBatis 将数据库生成的自增 id 回填到实体的id属性上,这在 GBase 8s 上可以正常工作,因为它的 JDBC 驱动实现了标准 JDBC 3.0 的getGeneratedKeys。如果回填失败,插入后实体拿不到 id,后面再基于这个 id 做操作就会出问题。

如果 Mapper XML 放在src/main/java目录里,需要改pom.xml让 Maven 把 XML 一起打进 classpath,否则运行时报Invalid bound statement (not found)。常见做法是把 XML 放src/main/resources/mapper下,这是最省心的方式,我一般不让 XML 和 java 混放。

4.3 Service 层测试事务回滚

写一个简单的 Service 来验证事务是否对 GBase 8s 生效。GBase 8s 默认是支持事务的,但要求表创建时带日志属性。如果你建表时用的是create table默认选项,并且表的日志模式是buffered,普通增删改在事务回滚时没有问题。下面是一个带事务的 Service 实现:

@Service public class UserService { @Autowired private UserMapper userMapper; @Transactional(rollbackFor = Exception.class) public void insertAndRollback(User user) { userMapper.insertUser(user); // 故意抛异常,观察事务是否回滚 throw new RuntimeException("rollback test"); } @Transactional(rollbackFor = Exception.class) public User insertAndReturn(User user) { userMapper.insertUser(user); return userMapper.selectById(user.getId()); } }

测试代码如下:

@SpringBootTest class UserServiceTest { @Autowired private UserService userService; @Autowired private UserMapper userMapper; @Test void testRollback() { User user = new User(); user.setName("rollback_user"); user.setAge(20); try { userService.insertAndRollback(user); } catch (Exception ignored) { } User dbUser = userMapper.selectById(user.getId()); Assertions.assertNull(dbUser); } }

这里的关键验证点:insertAndRollback插入后抛出异常,事务回滚后selectById应该查不到这条记录。如果这一步失败,说明 GBase 8s 的表没有开启日志,事务并没有真正生效。检查方式是用dbaccess执行select * from systables where tabname = 't_user',看表的sys_log属性是否为B(buffered log)或U(unbuffered log)。如果是NL(no log),事务就是假的,插入和回滚都不会生效。这是 GBase 8s 和 MySQL 一个很大的区别:MySQL 默认 InnoDB 引擎自带事务,而 GBase 8s 的表是否记日志取决于建表时的配置和实例配置。

如果建表时没开日志,又必须要事务,可以通过下面 SQL 修改表的日志模式:

alter table t_user lock mode row; alter table t_user type (buffered);

注意,alter table修改日志模式时,需要持有表锁,如果在运行中的生产环境操作,可能阻塞其他会话。demo 环境无所谓,想怎么改都行。

5. GBase 8s 集成避坑:驱动、SQL 与事务的 5 个常见问题

5.1 驱动报错ClassNotFound或No suitable driver怎么办

现象:应用启动或第一次获取连接时,日志抛出ClassNotFoundException: com.gbase.jdbc.Driver,或者Cannot load driver class: com.gbase.jdbc.Driver,以及No suitable driver found for jdbc:gbase://...。

原因:没有把驱动 jar 引入工程,或者引入的 jar 里没有这个类。还有一种情况是本地 Maven 仓库里有同名不同版本的 jar,但里面实际类名不同,导致运行时加载不到。另外,如果使用了systemscope 且打包时没有includeSystemScope=true,本地运行正常但部署环境启动直接报找不到驱动类。

解决:先用jar tf gbase-connector-java-xxx.jar | grep Driver.class查看驱动 jar 里的实际类路径,确认类名后再配置driver-class-name。检查mvn dependency:tree看依赖是否真的被解析了。部署后出问题,优先检查BOOT-INF/lib下有没有驱动 jar,没有就按第 2 章的方式重新打包。

5.2 连接超时:第一次连接特别慢,后面快

现象:应用启动时可以正常获取连接,但第一次查询耗时 10 秒以上,控制台没有报错,只是慢。换成 MySQL 后延迟只有几十毫秒,对比明显。

原因:GBase 8s 的 JDBC 驱动在初始化时会加载一些内部资源,比如字符集映射、sql 语法解析器,第一次调用时开销较大。另外,如果连接的库名是sysmaster或sysutils这类系统库,连接本身很快,但第一次 SQL 执行时可能要加载系统表的元数据,也会慢。

解决:连接池的minimum-idle不要设成 0,让连接池在启动时就初始化几个连接,把驱动首次加载的摊销成本放在应用启动阶段。其次,在 Service 初始化时主动执行一次select 1类似的探活 SQL,让各类内部缓存热起来。还有个实用技巧:连接池的initialization-fail-timeout要看具体连接池实现,Hikari 可以通过initializationFailTimeout > 0让启动时强制初始化连接,失败就抛异常。

5.3 表名或字段名大小写导致 SQL 执行失败

现象:报Unknown column "NAME"或Table "T_USER" doesn't exist,但建表时明明建的就是t_user和name。

原因:GBase 8s 对未加引号的标识符默认做小写处理,但如果你在 JDBC URL 或会话参数里开启了区分大小写的选项,或者数据库实例配置中启用了大小写敏感,行为就会变。更隐蔽的情况是:你用dbaccess建表时,表名是小写,程序里写的 SQL 是大写,在不敏感模式下没问题,在敏感模式下直接报错。

解决:统一 SQL 语句里的表名和字段名为小写。如果是已有的表,先查syscolumns确认实际存储的列名大小写:

select tabname, colname from syscolumns where tabname = 't_user';

如果确实是大小写混存的表,SQL 里给表名和列名加双引号,并且严格按存储的大小写来写。但这不是好习惯,建议规范建表,统一小写。

5.4useGeneratedKeys不回填自增 id

现象:insertUser执行后,实体的id属性还是 null,数据库里确实插入成功了。在 MySQL 上同样代码没问题,切到 GBase 8s 就失灵。

原因:JDBC 驱动对getGeneratedKeys()的支持不完整,或者 MyBatis 在解析 XML 时没匹配到正确的 key 属性。GBase 8s 的某些驱动版本,在批量插入时也拿不到自增 id。

解决:不用useGeneratedKeys,改为插入后查select LAST_INSERT_ID()等价函数。GBase 8s 里对应的函数是SELECT dbinfo('sqlca.sqlerrd1'),在事务内是安全的。也可以在 insert 语句中加returning子句:

insert into t_user (name, age) values (#{name}, #{age}) returning id;

配合 MyBatis 的selectKey使用,是最稳的方案。具体实现:

<insert id="insertUser"> insert into t_user (name, age) values (#{name}, #{age}) <selectKey keyProperty="id" resultType="java.lang.Integer" order="AFTER"> select dbinfo('sqlca.sqlerrd1') from systables where tabname = 't_user' </selectKey> </insert>

注意,这里的from systables where tabname = 't_user'只是为了让查询有上下文,实际值从 dbinfo 里取。这个方案比getGeneratedKeys更可靠,代价是每次插入多一次查询。

5.5 事务回滚不生效,数据照样进去了

现象:测试事务回滚时,异常抛出后数据仍然存在于数据库,和 Netkiller 风格的 MySQL 完全不同,让人怀疑 Spring 事务失效了。

原因:GBase 8s 默认建的表可能没有启日志,导致数据操作不在事务保护范围内。@Transactional确实生效了,但数据库层面不记日志,自然也就没有回滚可言。另外,如果 Service 方法没有被 Spring 管理,比如在非 Spring Bean 里调用this.insertAndRollback(...),事务注解也会失效,但报错现象不同,这里不展开。

解决:建表时显式指定日志模式。如果已经建完表,执行alter table t_user type (buffered)或者alter table t_user type (unbuffered),然后重测事务。还有一个坑要在验收时确认:GBase 8s 的 DDL 语句会自动提交,如果建表后没有把后续 DML 放在同一个事务里,回滚只能回滚事务内的操作,外部已经提交的 DDL 不会回滚。这符合 SQL 标准行为,但第一次接触的人容易误判为事务失控。

6. 把 demo 升级成能验证生产场景的脚本

6.1 用@SpringBootTest写一组全链路验证用例

代码跑通只是第一步,真正让人睡得着觉的是自动化验证。我把上面的增删改查扩成一个验证类,每次环境变更之后跑一遍,能省下大量的手工排查时间。

@SpringBootTest class GBase8sFullChainTest { @Autowired private UserService userService; @Autowired private UserMapper userMapper; @Test void testCrud() { User user = new User(); user.setName("smoke_test"); user.setAge(30); userService.insertAndReturn(user); User fetched = userMapper.selectById(user.getId()); Assertions.assertNotNull(fetched); Assertions.assertEquals("smoke_test", fetched.getName()); fetched.setAge(31); userMapper.updateUser(fetched); Assertions.assertEquals(31, userMapper.selectById(user.getId()).getAge()); userMapper.deleteById(user.getId()); Assertions.assertNull(userMapper.selectById(user.getId())); } }

这个测试覆盖了 Insert、Select、Update、Delete 四条完整链路。注意testRollback要和testCrud分开,因为回滚测试会故意抛异常,混在一起时如果异常处理不干净,后续断言会被跳过。测试方法名也要刻意区分,比如testTransactionRollbackForNoLogTable,一眼能看出这个用例测的是哪个场景。

跑测试时如果发现@SpringBootTest加载 Spring 上下文太慢,可以针对 DAO 层单独用 MyBatis 的原生 SqlSessionFactory 写测试,但 demo 阶段没必要,保持完整的 Spring 上下文反而更能暴露配置问题。

6.2 验证连接池参数与并发压测的最小脚本

demo 验证的不只是“能连上”,还要看连接池参数是否合理。写一个并发获取连接的小脚本比用 JMeter 轻量得多,适合在运维不给力的场合自证清白。

@SpringBootTest class ConnectionPoolPressureTest { @Autowired private DataSource dataSource; @Test void testParallelAcquire() throws Exception { ExecutorService pool = Executors.newFixedThreadPool(20); CountDownLatch start = new CountDownLatch(1); CountDownLatch done = new CountDownLatch(20); for (int i = 0; i < 20; i++) { pool.submit(() -> { try (Connection conn = dataSource.getConnection()) { start.await(); conn.createStatement().execute("select 1 from t_user where id = 1"); } catch (Exception e) { e.printStackTrace(); } finally { done.countDown(); } }); } start.countDown(); done.await(30, TimeUnit.SECONDS); } }

代码要点:start.await()是为了让 20 个线程同时抢连接,模拟并发高峰;done.await(30, TimeUnit.SECONDS)超时 30 秒内必须全部执行完,否则失败。如果频繁报连接超时或 SQL 执行异常,说明maximum-pool-size或connection-timeout需要调优。一般 GBase 8s 默认能承受的连接数取决于数据库实例配置,连接池开太大,不是让 GBase 8s 爽,而是让它的内存和进程数爆掉,数据源侧先炸,数据库侧后知后觉。建议先从小的maximum-pool-size开始,比如 10,再逐步往上加。

6.3 生产环境落地时还要补什么

demo 跑通后,生产化还有一些事要做:密码加密存储,不能明文写在 yml 里;配置中心统一管理数据库地址和账号;慢 SQL 日志开启,GBase 8s 的onstat -g sql能看当前会话执行的 SQL,必要时用这个命令确认是不是有会话没释放。另外一个经验是,JDBC 连接串里的characterEncoding如果不指定,GBase 8s 默认输出字符集可能和应用的 UTF-8 不一致,导致中文乱码。建议在 URL 后追加:gbasedb.charset=UTF-8;,但这需要驱动版本支持,不同版本写法略有区别。

这些收尾工作,我一般是在 demo 验证通过后立刻补上,而不是上线前再突击。毕竟一次 SQL 乱码引发的线上事故,比改配置要难看得多。最后想说的是,GBase 8s 的集成不算难,但很多坑都藏在驱动的具体实现里,多跑一次事务回滚测试、多看一眼 jar 里的类名,比反复猜配置靠谱得多——希望这篇对你有所帮助。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询