MyBatis 对多数据库的支持
MyBatis support for multiple databases
我有不同的客户使用不同的数据库供应商(postgres、oracle、mysql 等)
我想编写一次我的代码并能够运行针对不同的数据库。
实现此目标的 "mybatis" 方法是什么?
我目前发现的问题例如:
- postgres 在 create sql 语句中有一个 "if not exist" 的概念。甲骨文不支持。
- oracle 在 sql 语法中没有 "limit" 和 "offset" 支持,而其他数据库支持。
- DDL 语句中的文本(postgres)与 clob(oracle 和其他)jdbc 类型。
我不想重复我的查询(这是我目前所做的)。可能有更优雅的方法来做到这一点。
我用的是mybatis java注解
你是说你正在使用注释,但我建议为此使用 XML。我通常觉得它更清晰,尤其是因为无论如何您都需要完全更改查询的某些部分。
这是一个 DDL 示例,其中您为同一方法获得两个单独的 XML 元素,但数据库 ID 不同。这些 table 非常相似,但是由于您检查 table 是否已经存在的方式以及类型有很大不同,您无法真正避免为此使用不同的 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 使用单独的查询元素或在同一查询元素中仅使用条件片段之间进行选择是一个可读性问题。根据查询的复杂性,它可能非常主观。
例如,我发现以下 "upsert" 使用 PostgreSQL 和 SQL 服务器很难阅读。在单独的元素中会更好:
<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
中,您可以在与该目录匹配的包中使用 MyMapper
Java 接口。那里没有特定于数据库 ID 的内容。
package mypackage;
public interface MyMapper {
void createTables();
void deleteItem(@Param("gid") UUID gid);
Something getLatestSomething();
}
在 Spring 中使用 MyBatis 时,您可以像这样设置供应商配置:
@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
.
我有不同的客户使用不同的数据库供应商(postgres、oracle、mysql 等)
我想编写一次我的代码并能够运行针对不同的数据库。
实现此目标的 "mybatis" 方法是什么?
我目前发现的问题例如:
- postgres 在 create sql 语句中有一个 "if not exist" 的概念。甲骨文不支持。
- oracle 在 sql 语法中没有 "limit" 和 "offset" 支持,而其他数据库支持。
- DDL 语句中的文本(postgres)与 clob(oracle 和其他)jdbc 类型。
我不想重复我的查询(这是我目前所做的)。可能有更优雅的方法来做到这一点。
我用的是mybatis java注解
你是说你正在使用注释,但我建议为此使用 XML。我通常觉得它更清晰,尤其是因为无论如何您都需要完全更改查询的某些部分。
这是一个 DDL 示例,其中您为同一方法获得两个单独的 XML 元素,但数据库 ID 不同。这些 table 非常相似,但是由于您检查 table 是否已经存在的方式以及类型有很大不同,您无法真正避免为此使用不同的 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 使用单独的查询元素或在同一查询元素中仅使用条件片段之间进行选择是一个可读性问题。根据查询的复杂性,它可能非常主观。
例如,我发现以下 "upsert" 使用 PostgreSQL 和 SQL 服务器很难阅读。在单独的元素中会更好:
<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
中,您可以在与该目录匹配的包中使用 MyMapper
Java 接口。那里没有特定于数据库 ID 的内容。
package mypackage;
public interface MyMapper {
void createTables();
void deleteItem(@Param("gid") UUID gid);
Something getLatestSomething();
}
在 Spring 中使用 MyBatis 时,您可以像这样设置供应商配置:
@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
.