Add unit tests for dynamic-datasource-spring-boot-starter (#555)

This commit is contained in:
Ling Hengqian 2023-08-28 09:56:59 +08:00 committed by GitHub
parent 18b5312b68
commit 6ba32b2505
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 910 additions and 62 deletions

View File

@ -42,7 +42,7 @@ jobs:
cache: 'maven'
- name: Build test with Maven
run: |
./mvnw -am -pl dynamic-datasource-spring-boot3-starter -T1C -B clean test
./mvnw -T1C -B clean test
test-minimum-jdk-ci:
name: Test CI - JDK ${{ matrix.java-version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
@ -64,4 +64,4 @@ jobs:
./mvnw -am -pl dynamic-datasource-creator -T1C -B clean package
./mvnw -am -pl dynamic-datasource-spring -T1C -B clean package
./mvnw -am -pl dynamic-datasource-spring-boot-common -T1C -B clean package
./mvnw -am -pl dynamic-datasource-spring-boot-starter -T1C -B clean package
./mvnw -am -pl dynamic-datasource-spring-boot-starter -T1C -B clean test

View File

@ -27,7 +27,6 @@
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.18</version>
<optional>true</optional>
</dependency>
<dependency>
@ -35,6 +34,15 @@
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
</project>

View File

@ -0,0 +1,71 @@
/*
* Copyright © 2018 organization baomidou
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baomidou.dynamic.datasource.fixture;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.dynamic.datasource.creator.hikaricp.HikariCpConfig;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import org.springframework.test.context.junit4.SpringRunner;
import javax.sql.DataSource;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
@SpringBootTest(classes = AddRemoveDatasourceApplication.class, webEnvironment = RANDOM_PORT)
@RunWith(SpringRunner.class)
public class AddRemoveDatasourceTest {
@Autowired
DataSource dataSource;
@Autowired
DefaultDataSourceCreator dataSourceCreator;
@Test
public void testAddAndRemoveDataSource() {
HikariCpConfig hikariCpConfig = new HikariCpConfig();
hikariCpConfig.setConnectionTestQuery("select 1");
DataSourceProperty dataSourceProperty = new DataSourceProperty();
dataSourceProperty.setHikari(hikariCpConfig);
dataSourceProperty.setPoolName("slave_1");
dataSourceProperty.setUsername("sa");
dataSourceProperty.setPassword("");
dataSourceProperty.setType(SimpleDriverDataSource.class);
dataSourceProperty.setUrl("jdbc:h2:mem:test1;MODE=MySQL");
dataSourceProperty.setDriverClassName("org.h2.Driver");
DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
ds.addDataSource(dataSourceProperty.getPoolName(), dataSourceCreator.createDataSource(dataSourceProperty));
assertThat(ds.getDataSources().keySet()).contains("slave_1");
ds.removeDataSource("slave_1");
assertThat(ds.getDataSources().keySet()).doesNotContain("slave_1");
}
}
@SpringBootApplication
class AddRemoveDatasourceApplication {
public static void main(String[] args) {
SpringApplication.run(AddRemoveDatasourceApplication.class, args);
}
}

View File

@ -0,0 +1,97 @@
/*
* Copyright © 2018 organization baomidou
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baomidou.dynamic.datasource.fixture;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.dynamic.datasource.provider.AbstractJdbcDataSourceProvider;
import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.SimpleDriverDataSource;
import org.springframework.test.context.junit4.SpringRunner;
import javax.sql.DataSource;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashMap;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
@SpringBootTest(classes = LoadDatasourceFromJDBCApplication.class, webEnvironment = RANDOM_PORT)
@RunWith(SpringRunner.class)
public class LoadDatasourceFromJDBCTest {
@Autowired
DataSource dataSource;
@Test
public void testExistDataSource() {
DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
assertThat(ds.getDataSources().keySet()).contains("master", "db1", "db2", "db3");
}
}
@SuppressWarnings({"SqlDialectInspection", "SqlNoDataSourceInspection"})
@SpringBootApplication
class LoadDatasourceFromJDBCApplication {
public static void main(String[] args) {
SpringApplication.run(LoadDatasourceFromJDBCApplication.class, args);
}
@Bean
public DynamicDataSourceProvider dynamicDataSourceProvider(DefaultDataSourceCreator dataSourceCreator) {
return new AbstractJdbcDataSourceProvider(dataSourceCreator, "org.h2.Driver", "jdbc:h2:mem:test;MODE=MySQL", "sa", "") {
@Override
protected Map<String, DataSourceProperty> executeStmt(Statement statement) throws SQLException {
statement.execute("CREATE TABLE IF NOT EXISTS `DB`\n" +
"(\n" +
" `name` VARCHAR(30) NULL DEFAULT NULL,\n" +
" `username` VARCHAR(30) NULL DEFAULT NULL,\n" +
" `password` VARCHAR(30) NULL DEFAULT NULL,\n" +
" `url` VARCHAR(30) NULL DEFAULT NULL,\n" +
" `driver` VARCHAR(30) NULL DEFAULT NULL\n" +
")");
statement.executeUpdate("insert into DB values ('master','sa','','jdbc:h2:mem:test;MODE=MySQL','org.h2.Driver')");
statement.executeUpdate("insert into DB values ('db1','sa','','jdbc:h2:mem:test2','org.h2.Driver')");
statement.executeUpdate("insert into DB values ('db2','sa','','jdbc:h2:mem:test3','org.h2.Driver')");
statement.executeUpdate("insert into DB values ('db3','sa','','jdbc:h2:mem:test4','org.h2.Driver')");
Map<String, DataSourceProperty> map = new HashMap<>();
ResultSet rs = statement.executeQuery("select * from DB");
while (rs.next()) {
DataSourceProperty dataSourceProperty = new DataSourceProperty();
dataSourceProperty.setUsername(rs.getString("username"));
dataSourceProperty.setPassword(rs.getString("password"));
dataSourceProperty.setUrl(rs.getString("url"));
dataSourceProperty.setDriverClassName(rs.getString("driver"));
dataSourceProperty.setType(SimpleDriverDataSource.class);
map.put(rs.getString("name"), dataSourceProperty);
}
return map;
}
};
}
}

View File

@ -0,0 +1,95 @@
/*
* Copyright © 2018 organization baomidou
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baomidou.dynamic.datasource.fixture;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.dynamic.datasource.fixture.service.nest.SchoolService;
import com.baomidou.dynamic.datasource.fixture.service.nest.Student;
import com.baomidou.dynamic.datasource.fixture.service.nest.StudentService;
import com.baomidou.dynamic.datasource.fixture.service.nest.Teacher;
import com.baomidou.dynamic.datasource.fixture.service.nest.TeacherService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.sql.DataSource;
import java.util.Arrays;
import java.util.Collections;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
@SpringBootTest(classes = NestApplication.class, webEnvironment = RANDOM_PORT)
@RunWith(SpringRunner.class)
public class NestDataSourceTest {
@Autowired
DataSource dataSource;
@Autowired
DefaultDataSourceCreator dataSourceCreator;
@Autowired
TeacherService teacherService;
@Autowired
StudentService studentService;
@Autowired
SchoolService schoolService;
@Test
public void testNest() {
DataSourceProperty masterDataSourceProperty = createDataSourceProperty("master");
DataSourceProperty teacherDataSourceProperty = createDataSourceProperty("teacher");
DataSourceProperty studentDataSourceProperty = createDataSourceProperty("student");
DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
ds.addDataSource(masterDataSourceProperty.getPoolName(), dataSourceCreator.createDataSource(masterDataSourceProperty));
ds.addDataSource(teacherDataSourceProperty.getPoolName(), dataSourceCreator.createDataSource(teacherDataSourceProperty));
ds.addDataSource(studentDataSourceProperty.getPoolName(), dataSourceCreator.createDataSource(studentDataSourceProperty));
assertThat(ds.getDataSources().keySet()).contains("master", "teacher", "student");
assertThat(teacherService.addTeacherWithTx("ss", 1)).isEqualTo(1);
assertThat(studentService.addStudentWithTx("tt", 2)).isEqualTo(1);
assertThat(teacherService.selectTeachers()).isEqualTo(Collections.singletonList(new Teacher(1, "tt", 2)));
assertThat(studentService.selectStudents()).isEqualTo(Collections.singletonList(new Student(1, "tt", 2)));
assertThat(schoolService.addTeacherAndStudentWithTx()).isEqualTo(2);
assertThat(teacherService.selectTeachers()).isEqualTo(Arrays.asList(new Teacher(1, "tt", 2), new Teacher(2, "bb", 4)));
assertThat(studentService.selectStudents()).isEqualTo(Arrays.asList(new Student(1, "tt", 2), new Student(2, "bb", 4)));
}
private DataSourceProperty createDataSourceProperty(String poolName) {
DataSourceProperty result = new DataSourceProperty();
result.setPoolName(poolName);
result.setDriverClassName("org.h2.Driver");
result.setUrl("jdbc:h2:mem:test;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE;INIT=RUNSCRIPT FROM 'classpath:db/add-remove-datasource.sql'");
result.setUsername("sa");
result.setPassword("");
return result;
}
}
@SpringBootApplication
class NestApplication {
public static void main(String[] args) {
SpringApplication.run(NestApplication.class, args);
}
}

View File

@ -0,0 +1,118 @@
/*
* Copyright © 2018 organization baomidou
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baomidou.dynamic.datasource.fixture;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.dynamic.datasource.fixture.service.spel.CustomResultHandler;
import com.baomidou.dynamic.datasource.fixture.service.spel.User;
import com.baomidou.dynamic.datasource.fixture.service.spel.UserService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.web.context.WebApplicationContext;
import javax.sql.DataSource;
import java.nio.charset.StandardCharsets;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup;
@SpringBootTest(classes = SPELApplication.class, webEnvironment = RANDOM_PORT)
@RunWith(SpringRunner.class)
public class SPELTest {
MockMvc mockMvc;
@Autowired
private WebApplicationContext webApplicationContext;
@Autowired
DataSource dataSource;
@Autowired
DefaultDataSourceCreator dataSourceCreator;
@Autowired
UserService userService;
@Before
public void setup() {
this.mockMvc = webAppContextSetup(webApplicationContext)
.alwaysDo(new CustomResultHandler())
.build();
}
@Test
public void testSPEL() throws Exception {
DataSourceProperty masterDataSourceProperty = createDataSourceProperty("master");
DataSourceProperty tenant1_1DataSourceProperty = createDataSourceProperty("tenant1_1");
DataSourceProperty tenant1_2DataSourceProperty = createDataSourceProperty("tenant1_2");
DataSourceProperty tenant2_1DataSourceProperty = createDataSourceProperty("tenant2_1");
DataSourceProperty tenant2_2DataSourceProperty = createDataSourceProperty("tenant2_2");
DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
ds.addDataSource(masterDataSourceProperty.getPoolName(), dataSourceCreator.createDataSource(masterDataSourceProperty));
ds.addDataSource(tenant1_1DataSourceProperty.getPoolName(), dataSourceCreator.createDataSource(tenant1_1DataSourceProperty));
ds.addDataSource(tenant1_2DataSourceProperty.getPoolName(), dataSourceCreator.createDataSource(tenant1_2DataSourceProperty));
ds.addDataSource(tenant2_1DataSourceProperty.getPoolName(), dataSourceCreator.createDataSource(tenant2_1DataSourceProperty));
ds.addDataSource(tenant2_2DataSourceProperty.getPoolName(), dataSourceCreator.createDataSource(tenant2_2DataSourceProperty));
assertThat(ds.getDataSources().keySet()).contains("master", "tenant1_1", "tenant1_2", "tenant2_1", "tenant2_2");
mockMvc.perform(MockMvcRequestBuilders.get("/users/session").characterEncoding(StandardCharsets.UTF_8.name()))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().encoding(StandardCharsets.UTF_8.name()))
.andReturn().getResponse().getContentAsString();
mockMvc.perform(MockMvcRequestBuilders.get("/users/header").contentType(MediaType.APPLICATION_FORM_URLENCODED)
.header("tenantName", "tenant1")
.characterEncoding(StandardCharsets.UTF_8.name())
).andDo(print())
.andExpect(status().isOk())
.andExpect(content().encoding(StandardCharsets.UTF_8.name()))
.andReturn().getResponse().getContentAsString();
assertThat(userService.selectSpelByKey("tenant1")).isEqualTo("tenant1");
assertThat(userService.selecSpelByTenant(new User("tenant2"))).isEqualTo("tenant2");
}
private DataSourceProperty createDataSourceProperty(String poolName) {
DataSourceProperty result = new DataSourceProperty();
result.setPoolName(poolName);
result.setDriverClassName("org.h2.Driver");
result.setUrl("jdbc:h2:mem:test;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE;INIT=RUNSCRIPT FROM 'classpath:db/spring-expression-language.sql'");
result.setUsername("sa");
result.setPassword("");
return result;
}
}
@SpringBootApplication
class SPELApplication {
public static void main(String[] args) {
SpringApplication.run(SPELApplication.class, args);
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright © 2018 organization baomidou
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baomidou.dynamic.datasource.fixture.controller;
import com.baomidou.dynamic.datasource.fixture.service.spel.User;
import com.baomidou.dynamic.datasource.fixture.service.spel.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
@RequestMapping("/users")
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/session")
public List<User> session(HttpServletRequest request) {
request.getSession().setAttribute("tenantName", "tenant1");
return userService.selectSpelBySession();
}
@GetMapping("/header")
public String header() {
userService.selectSpelByHeader();
return "success";
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright © 2018 organization baomidou
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baomidou.dynamic.datasource.fixture.service.nest;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class SchoolService {
private final TeacherService teacherService;
private final StudentService studentService;
public SchoolService(TeacherService teacherService, StudentService studentService) {
this.teacherService = teacherService;
this.studentService = studentService;
}
@Transactional
public int addTeacherAndStudentWithTx() {
int aa = teacherService.addTeacherNoTx("aa", 3);
int bb = studentService.addStudentNoTx("bb", 4);
return aa + bb;
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright © 2018 organization baomidou
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baomidou.dynamic.datasource.fixture.service.nest;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Student {
private Integer id;
private String name;
private Integer age;
}

View File

@ -0,0 +1,75 @@
/*
* Copyright © 2018 organization baomidou
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baomidou.dynamic.datasource.fixture.service.nest;
import com.baomidou.dynamic.datasource.annotation.DS;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.LinkedList;
import java.util.List;
@SuppressWarnings({"SqlDialectInspection", "SqlNoDataSourceInspection"})
@Service
@DS("student")
public class StudentService {
private final DataSource dataSource;
public StudentService(DataSource dataSource) {
this.dataSource = dataSource;
}
@Transactional
public int addStudentWithTx(String name, Integer age) {
try (Connection connection = dataSource.getConnection(); PreparedStatement preparedStatement = connection.prepareStatement("insert into student (name,age) values (?,?)")) {
preparedStatement.setString(1, name);
preparedStatement.setInt(2, age);
return preparedStatement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public int addStudentNoTx(String name, Integer age) {
try (Connection connection = dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("insert into student (name,age) values (?,?)")) {
preparedStatement.setString(1, name);
preparedStatement.setInt(2, age);
return preparedStatement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public List<Student> selectStudents() {
List<Student> result = new LinkedList<>();
try (Connection connection = dataSource.getConnection(); Statement statement = connection.createStatement()) {
ResultSet resultSet = statement.executeQuery("SELECT * FROM student");
while (resultSet.next()) {
result.add(new Student(resultSet.getInt(1), resultSet.getString(2), resultSet.getInt(3)));
}
return result;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright © 2018 organization baomidou
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baomidou.dynamic.datasource.fixture.service.nest;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Teacher {
private Integer id;
private String name;
private Integer age;
}

View File

@ -0,0 +1,78 @@
/*
* Copyright © 2018 organization baomidou
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baomidou.dynamic.datasource.fixture.service.nest;
import com.baomidou.dynamic.datasource.annotation.DS;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.LinkedList;
import java.util.List;
@SuppressWarnings({"SqlNoDataSourceInspection", "SqlDialectInspection"})
@Service
@DS("teacher")
public class TeacherService {
private final DataSource dataSource;
public TeacherService(DataSource dataSource) {
this.dataSource = dataSource;
}
@Transactional
public int addTeacherWithTx(String name, Integer age) {
try (Connection connection = dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("insert into teacher (name,age) values (?,?)")) {
preparedStatement.setString(1, name);
preparedStatement.setInt(2, age);
return preparedStatement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public int addTeacherNoTx(String name, Integer age) {
try (Connection connection = dataSource.getConnection();
PreparedStatement preparedStatement = connection.prepareStatement("insert into teacher (name,age) values (?,?)")) {
preparedStatement.setString(1, name);
preparedStatement.setInt(2, age);
return preparedStatement.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
public List<Teacher> selectTeachers() {
List<Teacher> result = new LinkedList<>();
try (Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement()) {
ResultSet resultSet = statement.executeQuery("SELECT * FROM student");
while (resultSet.next()) {
result.add(new Teacher(resultSet.getInt(1), resultSet.getString(2), resultSet.getInt(3)));
}
return result;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -0,0 +1,13 @@
package com.baomidou.dynamic.datasource.fixture.service.spel;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultHandler;
import java.nio.charset.StandardCharsets;
public class CustomResultHandler implements ResultHandler {
@Override
public void handle(MvcResult result) {
result.getResponse().setCharacterEncoding(StandardCharsets.UTF_8.name());
}
}

View File

@ -0,0 +1,39 @@
/*
* Copyright © 2018 organization baomidou
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baomidou.dynamic.datasource.fixture.service.spel;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class User {
private Integer id;
private String name;
private Integer age;
private String tenantName;
public User(String tenantName) {
this.id = null;
this.name = null;
this.age = null;
this.tenantName = tenantName;
}
}

View File

@ -0,0 +1,80 @@
/*
* Copyright © 2018 organization baomidou
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.baomidou.dynamic.datasource.fixture.service.spel;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import org.springframework.stereotype.Service;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.LinkedList;
import java.util.List;
@SuppressWarnings({"SqlDialectInspection", "SqlNoDataSourceInspection", "UnusedReturnValue", "unused"})
@Service
@DS("slave")
public class UserService {
private final DataSource dataSource;
public UserService(DataSource dataSource) {
this.dataSource = dataSource;
}
@DS("#session.tenantName")
public List<User> selectSpelBySession() {
List<User> result = new LinkedList<>();
try (Connection connection = dataSource.getConnection(); Statement statement = connection.createStatement()) {
ResultSet resultSet = statement.executeQuery("select * from t_user");
while (resultSet.next()) {
result.add(new User(resultSet.getInt(1), resultSet.getString(2), resultSet.getInt(3), resultSet.getString(4)));
}
return result;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@DS("#header.tenantName")
public List<User> selectSpelByHeader() {
List<User> result = new LinkedList<>();
try (Connection connection = dataSource.getConnection(); Statement statement = connection.createStatement()) {
ResultSet resultSet = statement.executeQuery("select * from t_user");
while (resultSet.next()) {
result.add(new User(resultSet.getInt(1), resultSet.getString(2), resultSet.getInt(3), resultSet.getString(4)));
}
return result;
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
@DS("#tenantName")
public String selectSpelByKey(String tenantName) {
return DynamicDataSourceContextHolder.peek();
}
@DS("#user.tenantName")
public String selecSpelByTenant(User user) {
return DynamicDataSourceContextHolder.peek();
}
}

View File

@ -0,0 +1,15 @@
CREATE TABLE IF NOT EXISTS TEACHER
(
id BIGINT(20) NOT NULL AUTO_INCREMENT,
name VARCHAR(30) NULL DEFAULT NULL,
age INT(11) NULL DEFAULT NULL,
PRIMARY KEY (id)
);
CREATE TABLE IF NOT EXISTS STUDENT
(
id BIGINT(20) NOT NULL AUTO_INCREMENT,
name VARCHAR(30) NULL DEFAULT NULL,
age INT(11) NULL DEFAULT NULL,
PRIMARY KEY (id)
);

View File

@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS t_user
(
id BIGINT(20) NOT NULL AUTO_INCREMENT,
name VARCHAR(30) NULL DEFAULT NULL,
age INT(11) NULL DEFAULT NULL,
PRIMARY KEY (id)
);

View File

@ -33,7 +33,6 @@
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>1.2.18</version>
<optional>true</optional>
</dependency>
<dependency>
@ -44,7 +43,6 @@
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>${h2.version}</version>
<scope>test</scope>
</dependency>
<dependency>

View File

@ -18,17 +18,18 @@ package com.baomidou.dynamic.datasource.fixture;
import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
import com.baomidou.dynamic.datasource.creator.DefaultDataSourceCreator;
import com.baomidou.dynamic.datasource.fixture.service.nest.*;
import org.h2.tools.Server;
import com.baomidou.dynamic.datasource.fixture.service.nest.SchoolService;
import com.baomidou.dynamic.datasource.fixture.service.nest.Student;
import com.baomidou.dynamic.datasource.fixture.service.nest.StudentService;
import com.baomidou.dynamic.datasource.fixture.service.nest.Teacher;
import com.baomidou.dynamic.datasource.fixture.service.nest.TeacherService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@ -54,24 +55,9 @@ public class NestDataSourceTest {
@Test
void testNest() {
DataSourceProperty masterDataSourceProperty = new DataSourceProperty();
masterDataSourceProperty.setPoolName("master");
masterDataSourceProperty.setDriverClassName("org.h2.Driver");
masterDataSourceProperty.setUrl("jdbc:h2:mem:test;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE;INIT=RUNSCRIPT FROM 'classpath:db/add-remove-datasource.sql'");
masterDataSourceProperty.setUsername("sa");
masterDataSourceProperty.setPassword("");
DataSourceProperty teacherDataSourceProperty = new DataSourceProperty();
teacherDataSourceProperty.setPoolName("teacher");
teacherDataSourceProperty.setDriverClassName("org.h2.Driver");
teacherDataSourceProperty.setUrl("jdbc:h2:mem:test;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE;INIT=RUNSCRIPT FROM 'classpath:db/add-remove-datasource.sql'");
teacherDataSourceProperty.setUsername("sa");
teacherDataSourceProperty.setPassword("");
DataSourceProperty studentDataSourceProperty = new DataSourceProperty();
studentDataSourceProperty.setPoolName("student");
studentDataSourceProperty.setDriverClassName("org.h2.Driver");
studentDataSourceProperty.setUrl("jdbc:h2:mem:test;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE;INIT=RUNSCRIPT FROM 'classpath:db/add-remove-datasource.sql'");
studentDataSourceProperty.setUsername("sa");
studentDataSourceProperty.setPassword("");
DataSourceProperty masterDataSourceProperty = createDataSourceProperty("master");
DataSourceProperty teacherDataSourceProperty = createDataSourceProperty("teacher");
DataSourceProperty studentDataSourceProperty = createDataSourceProperty("student");
DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
ds.addDataSource(masterDataSourceProperty.getPoolName(), dataSourceCreator.createDataSource(masterDataSourceProperty));
ds.addDataSource(teacherDataSourceProperty.getPoolName(), dataSourceCreator.createDataSource(teacherDataSourceProperty));
@ -85,6 +71,16 @@ public class NestDataSourceTest {
assertThat(teacherService.selectTeachers()).isEqualTo(List.of(new Teacher(1, "tt", 2), new Teacher(2, "bb", 4)));
assertThat(studentService.selectStudents()).isEqualTo(List.of(new Student(1, "tt", 2), new Student(2, "bb", 4)));
}
private DataSourceProperty createDataSourceProperty(String poolName) {
DataSourceProperty result = new DataSourceProperty();
result.setPoolName(poolName);
result.setDriverClassName("org.h2.Driver");
result.setUrl("jdbc:h2:mem:test;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE;INIT=RUNSCRIPT FROM 'classpath:db/add-remove-datasource.sql'");
result.setUsername("sa");
result.setPassword("");
return result;
}
}
@SpringBootApplication

View File

@ -63,36 +63,11 @@ public class SPELTest {
@Test
void testSPEL() {
DataSourceProperty masterDataSourceProperty = new DataSourceProperty();
masterDataSourceProperty.setPoolName("master");
masterDataSourceProperty.setDriverClassName("org.h2.Driver");
masterDataSourceProperty.setUrl("jdbc:h2:mem:test;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE;INIT=RUNSCRIPT FROM 'classpath:db/spring-expression-language.sql'");
masterDataSourceProperty.setUsername("sa");
masterDataSourceProperty.setPassword("");
DataSourceProperty tenant1_1DataSourceProperty = new DataSourceProperty();
tenant1_1DataSourceProperty.setPoolName("tenant1_1");
tenant1_1DataSourceProperty.setDriverClassName("org.h2.Driver");
tenant1_1DataSourceProperty.setUrl("jdbc:h2:mem:test;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE;INIT=RUNSCRIPT FROM 'classpath:db/spring-expression-language.sql'");
tenant1_1DataSourceProperty.setUsername("sa");
tenant1_1DataSourceProperty.setPassword("");
DataSourceProperty tenant1_2DataSourceProperty = new DataSourceProperty();
tenant1_2DataSourceProperty.setPoolName("tenant1_2");
tenant1_2DataSourceProperty.setDriverClassName("org.h2.Driver");
tenant1_2DataSourceProperty.setUrl("jdbc:h2:mem:test;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE;INIT=RUNSCRIPT FROM 'classpath:db/spring-expression-language.sql'");
tenant1_2DataSourceProperty.setUsername("sa");
tenant1_2DataSourceProperty.setPassword("");
DataSourceProperty tenant2_1DataSourceProperty = new DataSourceProperty();
tenant2_1DataSourceProperty.setPoolName("tenant2_1");
tenant2_1DataSourceProperty.setDriverClassName("org.h2.Driver");
tenant2_1DataSourceProperty.setUrl("jdbc:h2:mem:test;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE;INIT=RUNSCRIPT FROM 'classpath:db/spring-expression-language.sql'");
tenant2_1DataSourceProperty.setUsername("sa");
tenant2_1DataSourceProperty.setPassword("");
DataSourceProperty tenant2_2DataSourceProperty = new DataSourceProperty();
tenant2_2DataSourceProperty.setPoolName("tenant2_2");
tenant2_2DataSourceProperty.setDriverClassName("org.h2.Driver");
tenant2_2DataSourceProperty.setUrl("jdbc:h2:mem:test;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE");
tenant2_2DataSourceProperty.setUsername("sa");
tenant2_2DataSourceProperty.setPassword("");
DataSourceProperty masterDataSourceProperty = createDataSourceProperty("master");
DataSourceProperty tenant1_1DataSourceProperty = createDataSourceProperty("tenant1_1");
DataSourceProperty tenant1_2DataSourceProperty = createDataSourceProperty("tenant1_2");
DataSourceProperty tenant2_1DataSourceProperty = createDataSourceProperty("tenant2_1");
DataSourceProperty tenant2_2DataSourceProperty = createDataSourceProperty("tenant2_2");
DynamicRoutingDataSource ds = (DynamicRoutingDataSource) dataSource;
ds.addDataSource(masterDataSourceProperty.getPoolName(), dataSourceCreator.createDataSource(masterDataSourceProperty));
ds.addDataSource(tenant1_1DataSourceProperty.getPoolName(), dataSourceCreator.createDataSource(tenant1_1DataSourceProperty));
@ -117,6 +92,16 @@ public class SPELTest {
assertThat(userService.selectSpelByKey("tenant1")).isEqualTo("tenant1");
assertThat(userService.selecSpelByTenant(new User("tenant2"))).isEqualTo("tenant2");
}
private DataSourceProperty createDataSourceProperty(String poolName) {
DataSourceProperty result = new DataSourceProperty();
result.setPoolName(poolName);
result.setDriverClassName("org.h2.Driver");
result.setUrl("jdbc:h2:mem:test;MODE=MySQL;DB_CLOSE_ON_EXIT=FALSE;INIT=RUNSCRIPT FROM 'classpath:db/spring-expression-language.sql'");
result.setUsername("sa");
result.setPassword("");
return result;
}
}
@SpringBootApplication

View File

@ -18,7 +18,6 @@ package com.baomidou.dynamic.datasource.fixture.controller;
import com.baomidou.dynamic.datasource.fixture.service.spel.User;
import com.baomidou.dynamic.datasource.fixture.service.spel.UserService;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@ -29,8 +28,11 @@ import java.util.List;
@RestController
public class UserController {
@Autowired
private UserService userService;
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/session")
public List<User> session(HttpServletRequest request) {

View File

@ -20,6 +20,7 @@ import org.springframework.transaction.annotation.Transactional;
@Service
public class SchoolService {
private final TeacherService teacherService;
private final StudentService studentService;

View File

@ -32,6 +32,7 @@ import java.util.List;
@Service
@DS("student")
public class StudentService {
private final DataSource dataSource;
public StudentService(DataSource dataSource) {

View File

@ -32,6 +32,7 @@ import java.util.List;
@Service
@DS("teacher")
public class TeacherService {
private final DataSource dataSource;
public TeacherService(DataSource dataSource) {

28
pom.xml
View File

@ -119,6 +119,16 @@
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-3-starter</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
@ -261,6 +271,24 @@
</build>
<profiles>
<profile>
<id>jdk11+</id>
<activation>
<jdk>[11,)</jdk>
</activation>
<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<argLine>--add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED</argLine>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</profile>
<profile>
<id>release</id>
<distributionManagement>