Spring Boot MyBatis-Plus实现数据库读写分离

梦里水乡 2024-03-13 ⋅ 26 阅读

引言

数据库读写分离是大型系统中常见的性能优化方案之一。通过将读操作和写操作分别分配到不同的数据库节点,可以有效地提高数据库的处理能力和稳定性。本文将介绍如何使用Spring Boot和MyBatis-Plus来实现数据库的读写分离。

1. 环境准备

在开始之前,需要准备以下环境:

  • JDK 1.8或以上版本
  • Maven构建工具
  • MySQL数据库

2. 创建Spring Boot项目

首先,我们需要创建一个Spring Boot项目。可以使用Maven命令创建一个基本的Spring Boot项目骨架:

mvn archetype:generate -DgroupId=com.example -DartifactId=read-write-split -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

然后在pom.xml文件中添加以下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.3.0</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

3. 配置数据源

由于需要使用多个数据库节点,因此我们需要配置多个数据源。在 application.properties 文件中添加以下配置:

# 数据源1
spring.datasource.master.url=jdbc:mysql://localhost:3306/db_master?characterEncoding=utf-8
spring.datasource.master.username=root
spring.datasource.master.password=root
spring.datasource.master.driver-class-name=com.mysql.jdbc.Driver

# 数据源2
spring.datasource.slave.url=jdbc:mysql://localhost:3306/db_slave?characterEncoding=utf-8
spring.datasource.slave.username=root
spring.datasource.slave.password=root
spring.datasource.slave.driver-class-name=com.mysql.jdbc.Driver

4. 配置MyBatis-Plus

application.properties文件中添加以下配置:

# MyBatis-Plus配置
mybatis-plus.mapper-locations=classpath:mapper/**/*.xml
mybatis-plus.global-config.db-config.id-type=auto
mybatis-plus.global-config.db-config.logic-delete-value=1
mybatis-plus.global-config.db-config.logic-not-delete-value=0

5. 定义实体类

com.example.readwritesplit.entity包中创建一个名为User的类,在其中定义数据库表user的字段:

@Data
@TableName("user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String name;
    
    private String email;
}

6. 创建Mapper接口

com.example.readwritesplit.mapper包中创建一个名为UserMapper的接口,并继承BaseMapper接口:

@Repository
public interface UserMapper extends BaseMapper<User> {
}

7. 创建Service接口和实现类

com.example.readwritesplit.service包中创建一个名为UserService的接口,并定义需要的方法:

public interface UserService {
    List<User> listAllUsers();
    
    void saveUser(User user);
}

然后在com.example.readwritesplit.service.impl包中创建一个名为UserServiceImpl的类,并实现UserService接口:

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public List<User> listAllUsers() {
        return userMapper.selectList(null);
    }

    @Override
    public void saveUser(User user) {
        userMapper.insert(user);
    }
}

8. 配置读写分离

com.example.readwritesplit.config包中创建一个名为DataSourceConfig的类,用于配置读写数据源:

@Configuration
public class DataSourceConfig {
    @Bean(name = "masterDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.master")
    public DataSource masterDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "slaveDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.slave")
    public DataSource slaveDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "routingDataSource")
    public DataSource routingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
                                        @Qualifier("slaveDataSource") DataSource slaveDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DatabaseType.MASTER, masterDataSource);
        targetDataSources.put(DatabaseType.SLAVE, slaveDataSource);
        
        RoutingDataSource routingDataSource = new RoutingDataSource();
        routingDataSource.setDefaultTargetDataSource(masterDataSource);
        routingDataSource.setTargetDataSources(targetDataSources);
        return routingDataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory(@Qualifier("routingDataSource") DataSource routingDataSource) throws Exception {
        MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
        sessionFactory.setDataSource(routingDataSource);
        sessionFactory.setTypeAliasesPackage("com.example.readwritesplit.entity");
        PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
        sessionFactory.setMapperLocations(resolver.getResources("classpath:mapper/**/*.xml"));
        return sessionFactory.getObject();
    }

    @Bean
    public PlatformTransactionManager transactionManager(@Qualifier("routingDataSource") DataSource routingDataSource) {
        return new DataSourceTransactionManager(routingDataSource);
    }
}

9. 实现读写分离的动态切换

com.example.readwritesplit.config包中创建一个名为RoutingDataSource的类,用于动态切换数据源:

public class RoutingDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceContext.getDatabaseType();
    }
}

com.example.readwritesplit.config包中创建一个名为DataSourceContext的类,用于保存当前数据源的信息:

public class DataSourceContext {
    private static final ThreadLocal<DatabaseType> CONTEXT = new ThreadLocal<>();

    public static void setDatabaseType(DatabaseType type) {
        CONTEXT.set(type);
    }

    public static DatabaseType getDatabaseType() {
        return CONTEXT.get();
    }

    public static void clearDatabaseType() {
        CONTEXT.remove();
    }
}

com.example.readwritesplit.config包中创建一个名为DataSourceAspect的类,用于在方法执行前根据需要切换数据源:

@Aspect
@Slf4j
@Component
public class DataSourceAspect {
    @Pointcut("execution(* com.example.readwritesplit.service.*.*(..))")
    public void servicePointCut() {
    }

    @Before("servicePointCut()")
    public void before(JoinPoint joinPoint) {
        Class<?> clazz = joinPoint.getTarget().getClass();
        DatabaseType databaseType = clazz.isAnnotationPresent(ReadOnly.class) ? DatabaseType.SLAVE : DatabaseType.MASTER;
        DataSourceContext.setDatabaseType(databaseType);
    }

    @After("servicePointCut()")
    public void after(JoinPoint joinPoint) {
        DataSourceContext.clearDatabaseType();
    }
}

添加@ReadOnly注解到读操作的方法上,示例如下:

@Repository
public interface UserMapper extends BaseMapper<User> {
    @ReadOnly // 表示查询操作
    @Select("SELECT * FROM user")
    List<User> selectList();
}

10. 测试读写分离

com.example.readwritesplit.controller包中创建一个名为UserController的类,用于测试读写分离效果:

@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/users")
    public List<User> listAllUsers() {
        return userService.listAllUsers();
    }

    @PostMapping("/user")
    public void saveUser(@RequestBody User user) {
        userService.saveUser(user);
    }
}

启动Spring Boot应用程序,然后使用以下URL测试读操作:

GET http://localhost:8080/users

使用以下URL测试写操作:

POST http://localhost:8080/user
Body: 
{
    "name": "John",
    "email": "john@example.com"
}

结论

通过Spring Boot和MyBatis-Plus的配合,我们可以很容易地实现数据库的读写分离。这使得我们可以在高并发的情况下提高数据库的性能和稳定性。希望本文对你有所帮助!


全部评论: 0

    我有话说: