数据库mysql基本知识
深入理解r2dbc在mysql中的使用
简介
mysql应该是我们在日常工作中使用到的一个非常普遍的数据库,虽然mysql现在是oracle公司的,但是它是开源的,市场占有率还是非常高的。
今天我们将会介绍r2dbc在mysql中的使用。
r2dbc-mysql的maven依赖
要想使用r2dbc-mysql,我们需要添加如下的maven依赖:
?
2
3
4
5
|
< dependency >
< groupId >dev.miku</ groupId >
< artifactId >r2dbc-mysql</ artifactId >
< version >0.8.2.RELEASE</ version >
</ dependency >
|
当然,如果你想使用snapshot版本的话,可以这样:
?
2
3
4
5
6
7
8
9
10
11
12
13
14
|
< dependency >
< groupId >dev.miku</ groupId >
< artifactId >r2dbc-mysql</ artifactId >
< version >${r2dbc-mysql.version}.BUILD-SNAPSHOT</ version >
</ dependency >
< repository >
< id >sonatype-snapshots</ id >
< name >SonaType Snapshots</ name >
< url > https://oss.sonatype.org/content/repositories/snapshots </ url >
< snapshots >
< enabled >true</ enabled >
</ snapshots >
</ repository >
|
创建connectionFactory
创建connectionFactory的代码实际上使用的r2dbc的标准接口,所以和之前讲到的h2的创建代码基本上是一样的:
?
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
ConnectionFactory connectionFactory = ConnectionFactories.get(
"r2dbcs: mysql://root:database-password-in-here@127.0.0.1:3306/r2dbc? " +
"zeroDate=use_round&" +
"sslMode=verify_identity&" +
"useServerPrepareStatement=true&" +
"tlsVersion=TLSv1.3%2CTLSv1.2%2CTLSv1.1&" +
"sslCa=%2Fpath%2Fto%2Fmysql%2Fca.pem&" +
"sslKey=%2Fpath%2Fto%2Fmysql%2Fclient-key.pem&" +
"sslCert=%2Fpath%2Fto%2Fmysql%2Fclient-cert.pem&" +
"sslKeyPassword=key-pem-password-in-here"
)
Mono<Connection> connectionMono = Mono.from(connectionFactory.create());
|
不同的是ConnectionFactories传入的参数不同。
我们也支持unix domain socket的格式:
?
2
3
4
|
ConnectionFactory connectionFactory = ConnectionFactories.get( "r2dbc: mysql://root@unix?unixSocket=%2Fpath%2Fto%2Fmysql.sock " )
Mono<Connection> connectionMono = Mono.from(connectionFactory.create());
|
同样的,我们也支持从ConnectionFactoryOptions中创建ConnectionFactory:
?
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
ConnectionFactoryOptions options = ConnectionFactoryOptions.builder()
.option(DRIVER, "mysql" )
.option(HOST, "127.0.0.1" )
.option(USER, "root" )
.option(PORT, 3306 )
.option(PASSWORD, "database-password-in-here" )
.option(DATABASE, "r2dbc" )
.option(CONNECT_TIMEOUT, Duration.ofSeconds( 3 ))
.option(SSL, true )
.option(Option.valueOf( "sslMode" ), "verify_identity" )
.option(Option.valueOf( "sslCa" ), "/path/to/mysql/ca.pem" )
.option(Option.valueOf( "sslCert" ), "/path/to/mysql/client-cert.pem" )
.option(Option.valueOf( "sslKey" ), "/path/to/mysql/client-key.pem" )
.option(Option.valueOf( "sslKeyPassword" ), "key-pem-password-in-here" )
.option(Option.valueOf( "tlsVersion" ), "TLSv1.3,TLSv1.2,TLSv1.1" )
.option(Option.valueOf( "sslHostnameVerifier" ), "com.example.demo.MyVerifier" )
.option(Option.valueOf( "sslContextBuilderCustomizer" ), "com.example.demo.MyCustomizer" )
.option(Option.valueOf( "zeroDate" ), "use_null" )
.option(Option.valueOf( "useServerPrepareStatement" ), true )
.option(Option.valueOf( "tcpKeepAlive" ), true )
.option(Option.valueOf( "tcpNoDelay" ), true )
.option(Option.valueOf( "autodetectExtensions" ), false )
.build();
ConnectionFactory connectionFactory = ConnectionFactories.get(options);
Mono<Connection> connectionMono = Mono.from(connectionFactory.create());
|
或者下面的unix domain socket格式:
?
2
3
4
5
6
7
8
9
|
ConnectionFactoryOptions options = ConnectionFactoryOptions.builder()
.option(DRIVER, "mysql" )
.option(Option.valueOf( "unixSocket" ), "/path/to/mysql.sock" )
.option(USER, "root" )
.build();
ConnectionFactory connectionFactory = ConnectionFactories.get(options);
Mono<Connection> connectionMono = Mono.from(connectionFactory.create());
|
使用MySqlConnectionFactory创建connection
上面的例子中,我们使用的是通用的r2dbc api来创建connection,同样的,我们也可以使用特有的MySqlConnectionFactory来创建connection:
?
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
MySqlConnectionConfiguration configuration = MySqlConnectionConfiguration.builder()
.host( "127.0.0.1" )
.user( "root" )
.port( 3306 )
.password( "database-password-in-here" )
.database( "r2dbc" )
.serverZoneId(ZoneId.of( "Continent/City" ))
.connectTimeout(Duration.ofSeconds( 3 ))
.sslMode(SslMode.VERIFY_IDENTITY)
.sslCa( "/path/to/mysql/ca.pem" )
.sslCert( "/path/to/mysql/client-cert.pem" )
.sslKey( "/path/to/mysql/client-key.pem" )
.sslKeyPassword( "key-pem-password-in-here" )
.tlsVersion(TlsVersions.TLS1_3, TlsVersions.TLS1_2, TlsVersions.TLS1_1)
.sslHostnameVerifier(MyVerifier.INSTANCE)
.sslContextBuilderCustomizer(MyCustomizer.INSTANCE)
.zeroDateOption(ZeroDateOption.USE_NULL)
.useServerPrepareStatement()
.tcpKeepAlive( true )
.tcpNoDelay( true )
.autodetectExtensions( false )
.extendWith(MyExtension.INSTANCE)
.build();
ConnectionFactory connectionFactory = MySqlConnectionFactory.from(configuration);
Mono<Connection> connectionMono = Mono.from(connectionFactory.create());
|
或者下面的unix domain socket方式:
?
2
3
4
5
6
7
8
|
MySqlConnectionConfiguration configuration = MySqlConnectionConfiguration.builder()
.unixSocket( "/path/to/mysql.sock" )
.user( "root" )
.build();
ConnectionFactory connectionFactory = MySqlConnectionFactory.from(configuration);
Mono<Connection> connectionMono = Mono.from(connectionFactory.create());
|
执行statement
首先看一个简单的不带参数的statement:
?
2
|
connection.createStatement( "INSERT INTO `person` (`first_name`, `last_name`) VALUES ('who', 'how')" )
.execute();
|
然后看一个带参数的statement:
?
2
3
4
5
6
7
8
9
|
connection.createStatement( "INSERT INTO `person` (`birth`, `nickname`, `show_name`) VALUES (?, ?name, ?name)" )
.bind( 0 , LocalDateTime.of( 2019 , 6 , 25 , 12 , 12 , 12 ))
.bind( "name" , "Some one" )
.add()
.bind( 0 , LocalDateTime.of( 2009 , 6 , 25 , 12 , 12 , 12 ))
.bind( 1 , "My Nickname" )
.bind( 2 , "Naming show" )
.returnGeneratedValues( "generated_id" )
.execute();
|
注意,如果参数是null的话,可以使用bindNull来进行null值的绑定。
接下来我们看一个批量执行的操作:
?
2
3
4
|
connection.createBatch()
.add( "INSERT INTO `person` (`first_name`, `last_name`) VALUES ('who', 'how')" )
.add( "UPDATE `earth` SET `count` = `count` + 1 WHERE `id` = 'human'" )
.execute();
|
执行事务
我们看一个执行事务的例子:
?
2
3
4
5
6
7
8
9
10
11
12
13
14
|
connection.beginTransaction()
.then(Mono.from(connection.createStatement( "INSERT INTO `person` (`first_name`, `last_name`) VALUES ('who', 'how')" ).execute()))
.flatMap(Result::getRowsUpdated)
.thenMany(connection.createStatement( "INSERT INTO `person` (`birth`, `nickname`, `show_name`) VALUES (?, ?name, ?name)" )
.bind( 0 , LocalDateTime.of( 2019 , 6 , 25 , 12 , 12 , 12 ))
.bind( "name" , "Some one" )
.add()
.bind( 0 , LocalDateTime.of( 2009 , 6 , 25 , 12 , 12 , 12 ))
.bind( 1 , "My Nickname" )
.bind( 2 , "Naming show" )
.returnGeneratedValues( "generated_id" )
.execute())
.flatMap(Result::getRowsUpdated)
.then(connection.commitTransaction());
|
使用线程池
为了提升数据库的执行效率,减少建立连接的开销,一般数据库连接都会有连接池的概念,同样的r2dbc也有一个叫做r2dbc-pool的连接池。
r2dbc-pool的依赖:
?
2
3
4
5
|
< dependency >
< groupId >io.r2dbc</ groupId >
< artifactId >r2dbc-pool</ artifactId >
< version >${version}</ version >
</ dependency >
|
如果你想使用snapshot版本,也可以这样指定:
?
2
3
4
5
6
7
8
9
10
11
|
< dependency >
< groupId >io.r2dbc</ groupId >
< artifactId >r2dbc-pool</ artifactId >
< version >${version}.BUILD-SNAPSHOT</ version >
</ dependency >
< repository >
< id >spring-libs-snapshot</ id >
< name >Spring Snapshot Repository</ name >
< url > https://repo.spring.io/libs-snapshot </ url >
</ repository >
|
看一下怎么指定数据库连接池:
?
2
3
|
ConnectionFactory connectionFactory = ConnectionFactories.get( "r2dbc:pool:<my-driver>://<host>:<port>/<database>[?maxIdleTime=PT60S[&…]" );
Publisher<? extends Connection> connectionPublisher = connectionFactory.create();
|
可以看到,我们只需要在连接URL上面添加pool这个driver即可。
同样的,我们也可以通过ConnectionFactoryOptions来创建:
?