MyBatis支持多个数据库

2022-08-15 00:00:00 postgresql oracle ibatis mybatis

我有不同的客户使用不同的数据库供应商(Postgres、Oracle、MySQL等)

我希望只编写一次代码,并且能够在不同的数据库上运行。

实现这一目标的"mybatis"方法是什么?

我到目前为止发现的问题,例如:

  • Postgres在CREATE SQL语句中有一个"如果不存在"的概念。Oracle不支持此功能。
  • Oracle在SQL语法中不支持"Limit"和"Offset",而其他数据库则支持。
  • DDL语句中的文本(Postgres)与lob(Oracle和其他)JDBC类型。

我不想重复我的查询(这就是我到目前为止所做的)。也许有一种更优雅的方式可以做到这一点。

我正在使用mybatis Java批注。


解决方案

您说您使用的是批注,但我建议您使用XML语言。我通常觉得它更清楚,特别是因为查询中的某些部分无论如何都需要完全更改。

下面是一个DDL示例,其中为相同的方法获取了两个不同的XML元素,但数据库ID不同。这些表非常相似,但由于您检查表是否已经存在以及类型是否完全不同的方式,您无法真正避免为此使用不同的SQL代码:

<update id="createTables" databaseId="postgresql">
    DO $$
    BEGIN
        CREATE TABLE IF NOT EXISTS item (
            id SERIAL PRIMARY KEY,
            content TEXT,
            creation_datetime TIMESTAMPTZ DEFAULT NOW(),
            modification_datetime TIMESTAMPTZ
        );
    END$$
</update>
<update id="createTables" databaseId="sqlserver">
    IF NOT EXISTS (SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = N'item')
    BEGIN
        CREATE TABLE item (
            id INT IDENTITY PRIMARY KEY,
            content NVARCHAR(MAX),
            creation_datetime DATETIMEOFFSET DEFAULT SYSDATETIMEOFFSET(),
            modification_datetime DATETIMEOFFSET
        );
    END
</update>

您也可以将查询放在一个SQL元素中,但根据数据库ID使用<if>(或<choose>)更改不同的节。如果更改很小,则效果很好:

<delete id="deleteItem">
    DELETE FROM my_item
    WHERE gid=CAST(#{gid} AS <if test="_databaseId == 'postgresql'">UUID</if><if test="_databaseId == 'sqlserver'">UNIQUEIDENTIFIER</if>)
</delete>

<select id="getLatestSomething" resultType="test.Something">
    SELECT <if test="_databaseId == 'sqlserver'">TOP 1</if> *
    FROM something
    ORDER BY creation_datetime DESC
    <if test="_databaseId == 'postgresql'">
    LIMIT 1
    </if>
</select>
对于不同的数据库ID使用不同的查询元素,还是在相同的查询元素中只使用条件片段,这是一个可读性问题。它可能非常主观,具体取决于查询的复杂程度。

例如,我发现下面使用PostgreSQL和SQL Server的"upsert"很难读懂。最好放在单独的元素中:

<insert id="insertStuff" parameterType="somestuff.Stuff">
    <if test="_databaseId == 'postgresql'">
        INSERT INTO my_stuff (...)
    </if>
    <if test="_databaseId == 'sqlserver'">
        MERGE INTO my_stuff WITH (HOLDLOCK) AS t USING (
    </if>
            VALUES (#{...},
                    <if test="_databaseId == 'postgresql'">
                        CAST(#{jsonData} AS JSONB)
                    </if>
                    <if test="_databaseId == 'sqlserver'">
                        #{jsonData}
                    </if>
            )
    <if test="_databaseId == 'postgresql'">
            ON CONFLICT DO NOTHING
    </if>
    <if test="_databaseId == 'sqlserver'">
        )
              AS s (...)
              ON t....=s....
                 AND t....=s....
        WHEN NOT MATCHED BY TARGET THEN
            INSERT (...)
            VALUES (s...., s....);
    </if>
</insert>

MyBatis Dynamic SQL documentation中有关于所有这些的更多信息。


假设您的XML映射器文件位于mypackage/MyMapper.xml中,则可以在与该目录匹配的包中使用MyMapperJava接口。没有特定于该数据库ID的内容。

package mypackage;

public interface MyMapper {
    void createTables();
    void deleteItem(@Param("gid") UUID gid);
    Something getLatestSomething();
}

将MyBatis与Spring配合使用时,可以这样设置供应商配置:

@Bean
public VendorDatabaseIdProvider vendorDatabaseIdProvider() {
    Properties vendorProperties = new Properties();
    vendorProperties.setProperty("PostgreSQL", "postgresql");
    vendorProperties.setProperty("SQL Server", "sqlserver");
    // Add others as required, this will look for the substring in the product name coming
    // from the database metadata.

    // ...
    VendorDatabaseIdProvider dbIdProvider = new VendorDatabaseIdProvider();
    dbIdProvider.setProperties(vendorProperties);
    return dbIdProvider;
}

@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource, ApplicationContext appContext,
        VendorDatabaseIdProvider vendorDatabaseIdProvider) throws Exception {
    SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
    bean.setDataSource(dataSource);
    bean.setDatabaseIdProvider(vendorDatabaseIdProvider);

    SqlSessionFactory factory = bean.getObject();
    return factory;
}

如果您没有使用Spring,您应该能够使用XML configuration配置DatabaseIdProvider

您并不严格需要数据库ID提供程序。它所做的就是根据从DataSource获得的产品名称在配置中设置数据库ID。大致是这样的:

String databaseId = databaseIdProvider.getDatabaseId(dataSource);
configuration.setDatabaseId(databaseId);

(如果查看org.apache.ibatis.mapping.VendorDatabaseIdProvider的代码,您会发现databaseIdProvider.getDatabaseId(...)只是在DatabaseMetaData.getDatabaseProductName()返回的内容中查找匹配子字符串。如果需要,您也可以通过其他设置手动完成此操作。)

请注意,当databaseId=""直接用作XML元素的属性时,没有下划线,但当用作测试条件时,称为_databaseId

相关文章