If there is a way but no technique, the technique can still be sought; if there is a technique but no way, it will stop at the technique.
Article directory
Preface
In the process of actually developing software products, the type of database may not be certain, and some customers may require which database must be used. For example, many government agencies require the use of domestic databases , so we need to adapt to a variety of databases during development.
MySQL
, Oracle
, PostgreSQL
, Dameng and other databases are based on the standards set by the American National Bureau of Standards when performing additions, deletions, modifications, and searches , SQL
such as SQL-92
.SQL-99
However, the actual SQL
differences between each database manufacturer will be small, that is, the database dialect. The most well-known is MySQL
the use of paging limit
and Oracle
the use of paging rownum
.
MyBatis-Plus
Supports various standard SQL
databases. Next we will actually demonstrate how to use MyBatis-Plus
and adapt to various databases.
case analysis
1. Pagination
Many databases SQL
use paging in different ways. MyBatis-Plus
The built-in paging plug-in PaginationInnerInterceptor
supports a variety of databases. The official website explains:
When using the built-in paging plug-in , you can set the type of database:
@Configuration
@MapperScan("com.pearl.pay.mapper") //持久层扫描
@EnableTransactionManagement //启用事务管理
public class MybatisPlusConfig {
@Bean
@ConditionalOnMissingBean(MybatisPlusInterceptor.class)
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
paginationInnerInterceptor.setDbType(DbType.MYSQL);
interceptor.addInnerInterceptor(paginationInnerInterceptor);
return interceptor;
}
}
When the built-in paging plug-in is executed SQL
, it will obtain the paging dialect based on the current database type .
In the paging dialect factory classDialectFactory
, you can see the specific dialect acquisition logic. Databases such as mysql
, mariadb
, clickhouse
, oceanbase
etc. all use mysql
dialects.
oracle
, 达梦数据库
using oracle
dialect:
MYSQL
Database paging statement uses LIMIT
assembly:
ORACLE
Database paging statement uses ROWNUM、ROW_ID
assembly:
In summary: When paging, to adapt to multiple databases, you only need to set the database type in the paging plug-in.
2. XML custom SQL
When calling , such as when MP
adding API
, deleting, modifying, or checking xxMpper.selectList()
, because the basic standards are used during MP
construction , there is generally no compatibility problem. SQL
But when we XML
write it ourselves in the file SQL
, we need to pay attention to various database matching and compatibility issues.
Mybatis
It has already provided multi-database support. You only need to tell the framework which database is used, and different statements can be executed according to different database vendors.
Mybatis
The DatabaseIdProvider
(Database Vendor Identity Provider) interface declares a method for obtaining the vendor identity. The identity can be used to later build different queries for each database type. This mechanism supports multiple vendors or versions.
public interface DatabaseIdProvider {
default void setProperties(Properties p) {
// NOP
}
// 根据数据源获取数据库厂商标识
String getDatabaseId(DataSource dataSource) throws SQLException;
}
Mybatis
VendorDatabaseIdProvider
Implementation classes are also provided :
public class VendorDatabaseIdProvider implements DatabaseIdProvider {
// 支持的数据库厂商(需要自己定义),比如: Oracle=》oracle
private Properties properties;
// 获取数据库厂商标识ID(databaseId),eg:oracle
@Override
public String getDatabaseId(DataSource dataSource) {
if (dataSource == null) {
throw new NullPointerException("dataSource cannot be null");
}
try {
return getDatabaseName(dataSource);
} catch (Exception e) {
LogHolder.log.error("Could not get a databaseId from dataSource", e);
}
return null;
}
@Override
public void setProperties(Properties p) {
this.properties = p;
}
// 根据产品名称,获取对应的databaseId。
private String getDatabaseName(DataSource dataSource) throws SQLException {
String productName = getDatabaseProductName(dataSource);
if (this.properties != null) {
for (Map.Entry<Object, Object> property : properties.entrySet()) {
if (productName.contains((String) property.getKey())) {
return (String) property.getValue();
}
}
// no match, return null
return null;
}
return productName;
}
// 从数据源中获取数据库产品名称,比如: Oracle
private String getDatabaseProductName(DataSource dataSource) throws SQLException {
try (Connection con = dataSource.getConnection()) {
DatabaseMetaData metaData = con.getMetaData();
return metaData.getDatabaseProductName();
}
}
}
Mybatis
When XML
writing in , SQL
there is an databaseId
attribute that can specify which database type the current statement block belongs to, such as:
<mapper namespace="org.pearl.mybatis.demo.dao.UserMapper">
<select id="selectOneById" resultType="org.pearl.mybatis.demo.pojo.entity.User" databaseId="mysql">
select * from user where user_id = #{id}
</select>
</mapper>
To sum up: we only need to configure DatabaseIdProvider
which databases are supported in the database, and then XML
write query statements for each database dialect in the database, and add databaseId
attributes. Mybatis
We will get the type of database used by the data source at startup, and then execute the statement corresponding to the current database configured.
Case presentation
1. Configuration
Building a project, integrating MP
, Oracle
, Mysql
is very simple, so I won’t go into details here.
In the configuration file, add the corresponding Oracle
connection Mysql
address:
spring:
datasource:
type: com.zaxxer.hikari.HikariDataSource
driver-class-name: oracle.jdbc.OracleDriver
url: jdbc:oracle:thin:@127.0.0.1:1521:ORCL
username: root
password: root
#driver-class-name: com.mysql.cj.jdbc.Driver
#url: jdbc:mysql://127.0.0.1:3306/d_account?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
#username: root
#password: root
Add configuration class in MP
:
@Configuration
@MapperScan("com.pearl.pay.mapper") //持久层扫描
@EnableTransactionManagement //启用事务管理
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor(DataSource dataSource,DatabaseIdProvider databaseIdProvider) throws SQLException {
// MP插件
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor();
// 获取当前数据源对应的数据库类型,添加分页插件
String databaseId = databaseIdProvider.getDatabaseId(dataSource);
DbType dbType = DbType.getDbType(databaseId);
paginationInnerInterceptor.setDbType(dbType);
interceptor.addInnerInterceptor(paginationInnerInterceptor);
return interceptor;
}
@Bean
public DatabaseIdProvider databaseIdProvider() {
// 数据库厂商提供者
DatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();
Properties p = new Properties();
p.setProperty("Oracle", "oracle");
p.setProperty("Mysql", "mysql");
databaseIdProvider.setProperties(p);
return databaseIdProvider;
}
}
2. Simple paging query
Because MyBatis-Plus
the built-in paging plug-in has been adapted, simple (without database dialect) paging query does not need to be adapted by writing your own code.
First add a paging query:
IPage<User> test(Page<User> page);
<select id="test" resultType="com.pearl.entity.User">
select * from user
</select>
The data source is configured Oracle
, but there is no configuration databaseId
. The test SQL
statement is printed as follows:
SELECT * FROM ( SELECT TMP.*, ROWNUM ROW_ID FROM ( select * from user) TMP WHERE ROWNUM <=10) WHERE ROW_ID > 0
The data source is configured as follows Mysql
, and the test SQL
statement is printed as follows:
select * from user LIMIT 10
3. Paging query with dialects
Requirement : Query data whose time node is less than the current time.
Oracle
The function is used at the current time sysdate
, and the function Mysql
is used now()
. At this time, you need to manually make it compatible.
Write two query statements with the same name, written for different database vendors SQL
, and configure the correspondingdatabaseId
<select id="test" resultType="com.pearl.entity.User" databaseId="mysql">
select * from user t <![CDATA[ where t.create_time <= now()]]>
</select>
<select id="test" resultType="com.pearl.entity.User" databaseId="oracle">
select * from user t <![CDATA[ where t.create_time <= sysdate ]]>
</select>
You can also use if
statements to determine the current database type and add different statements (recommended).
<select id="test" resultType="com.pearl.entity.User">
select * from user t
<where>
<if test="_databaseId == 'mysql'">
<![CDATA[ AND t.create_time <= now()]]>
</if>
<if test="_databaseId == 'oracle'">
<![CDATA[ AND t.create_time <= sysdate ]]>
</if>
</where>
</select>
To switch databases, execute as follows:
select * from user t where t.create_time <= now() LIMIT 10
SELECT * FROM ( SELECT TMP.*, ROWNUM ROW_ID FROM ( select * from user t where t.create_time <= sysdate ) TMP WHERE ROWNUM <=10) WHERE ROW_ID > 0
reference
Next, let's briefly understand some of the differences between Oracle
and Mysql
to facilitate development.
Data type comparison:
database | Comparative item | type |
---|---|---|
MySQL | type of data | INTEGER、SMALLINT、TINYINT、MEDIUMINT、BIGINT |
Oracle | type of data | number |
MySQL | date and time | date、timestamp、timestamp |
Oracle | date and time | date、timestamp |
MySQL | Character type | char、varchar |
Oracle | Character type | char、varchar、varchar2、nvarchar、nvarchar2 |
MySQL | large field | LONGTEXT |
Oracle | large field | clob |
Commonly used function comparison:
database | Comparative item | function |
---|---|---|
MySQL | Get string length | char_length(str) |
Oracle | Get string length | length(str) |
MySQL | Generate random sequence | UUID() |
Oracle | Generate random sequence | sys_guid() |
MySQL | Convert time to string | date_format(NOW(),‘%Y-%m-%d’) |
Oracle | Convert time to string | to_char(sysdate, ‘YYYY-MM-DD’) |
MySQL | Convert string time to time | str_to_date(‘2019-01-01’,‘%Y-%m-%d’) |
Oracle | Convert string time to time | to_date(‘2019-01-01’, ‘YYYY-MM-DD’) |
MySQL | Function conversion containing hours, minutes and seconds | date_format(NOW(),‘%Y-%m-%d %H:%i:%s’) |
Oracle | Function conversion containing hours, minutes and seconds | str_to_date(‘2019-01-01’,‘%Y-%m-%d %H:%i:%s’) |
MySQL | current time | now() |
Oracle | current time | sysdate |
other:
database | Comparative item | support |
---|---|---|
MySQL | quotation marks | Double quotes and single quotes |
Oracle | quotation marks | Only single quotes are recognized |
MySQL | String concatenation | concat() function |
Oracle | String concatenation | Double vertical bars can be used to connect strings |
MySQL | Pagination | limit |
Oracle | Pagination | rownum |