通过引入主从数据库同步系统,可以显著提升平台的性能和稳定性,同时保证数据的一致性和安全性。Druid连接池也提供了强大的监控和安全防护功能,使得整个系统更加健壮和可靠。
我们为什么选择Druid?
-
高效的连接管理:Druid 提供了高效的物理连接复用机制,能够快速获取和释放数据库连接,减少连接建立和关闭的开销。
-
异步初始化:支持异步初始化连接池,提高应用启动速度。
-
详细的统计信息:Druid 内置了丰富的统计功能,可以收集 SQL 执行情况、慢查询记录等详细数据,帮助我们更好地进行性能调优。
-
防火墙规则:支持配置防火墙规则,限制哪些 IP 地址可以访问数据库,增强安全性。
-
SQL 防注入:提供 WallFilter 插件,防止 SQL 注入攻击,保护数据库不受恶意 SQL 的影响。
-
多数据源支持:Druid 支持配置多个数据源,非常适合实现读写分离等高级场景。在我们的项目中,主从数据库的配置正是利用了这一特性。
代码实操
<dependencies><!-- Spring Boot Starter Web --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- MyBatis Plus --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.2</version></dependency><!-- Druid --><dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-starter</artifactId><version>1.2.8</version></dependency><!-- MySQL Connector --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency>
</dependencies>
application.yml
server:port:8080spring:
datasource:type:com.alibaba.druid.pool.DruidDataSourcedruid:master:url:jdbc:mysql://localhost:3306/master_db?useSSL=false&serverTimezone=UTCusername:rootpassword:rootslave:url:jdbc:mysql://localhost:3307/slave_db?useSSL=false&serverTimezone=UTCusername:rootpassword:rootmybatis-plus:
configuration:log-impl:org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations:classpath*:mybatis-mapper.xml
动态数据源上下文持有者
package com.example.demo.config;publicclass DynamicDataSourceContextHolder {privatestaticfinal ThreadLocal<String> contextHolder = new ThreadLocal<>();public static void setDataSourceType(String dataSourceType) {contextHolder.set(dataSourceType);}public static String getDataSourceType() {return contextHolder.get();}public static void clearDataSourceType() {contextHolder.remove();}
}
数据源配置
package com.example.demo.config;import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;@Configuration
publicclass DataSourceConfig {@Value("${spring.datasource.druid.master.url}")private String masterUrl;@Value("${spring.datasource.druid.master.username}")private String masterUsername;@Value("${spring.datasource.druid.master.password}")private String masterPassword;@Value("${spring.datasource.druid.slave.url}")private String slaveUrl;@Value("${spring.datasource.druid.slave.username}")private String slaveUsername;@Value("${spring.datasource.druid.slave.password}")private String slavePassword;@Beanpublic DataSource dynamicDataSource() {Map<Object, Object> targetDataSources = new HashMap<>();targetDataSources.put("master", masterDataSource());targetDataSources.put("slave", slaveDataSource());AbstractRoutingDataSource abstractRoutingDataSource = new AbstractRoutingDataSource() {@Overrideprotected Object determineCurrentLookupKey() {return DynamicDataSourceContextHolder.getDataSourceType();}};abstractRoutingDataSource.setDefaultTargetDataSource(masterDataSource());abstractRoutingDataSource.setTargetDataSources(targetDataSources);return abstractRoutingDataSource;}@Beanpublic DataSource masterDataSource() {DruidDataSource dataSource = new DruidDataSource();dataSource.setUrl(masterUrl);dataSource.setUsername(masterUsername);dataSource.setPassword(masterPassword);return dataSource;}@Beanpublic DataSource slaveDataSource() {DruidDataSource dataSource = new DruidDataSource();dataSource.setUrl(slaveUrl);dataSource.setUsername(slaveUsername);dataSource.setPassword(slavePassword);return dataSource;}
}
实体类
package com.example.demo.entity;import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;@TableName("product")
publicclass Product {@TableId(type = IdType.AUTO)private Long id;private String name;private Double price;// getters and setters
}
Mapper接口
package com.example.demo.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.demo.entity.Product;public interface ProductMapper extends BaseMapper<Product> {
}
mybatis-mapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.ProductMapper"><select id="selectById" resultType="com.example.demo.entity.Product">SELECT * FROM product WHERE id = #{id}</select><insert id="insert" parameterType="com.example.demo.entity.Product">INSERT INTO product (name, price) VALUES (#{name}, #{price})</insert></mapper>
Service接口
package com.example.demo.service;import com.example.demo.entity.Product;public interface ProductService {Product getProductById(Long id);boolean saveProduct(Product product);
}
Service
package com.example.demo.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.demo.config.DynamicDataSourceContextHolder;
import com.example.demo.entity.Product;
import com.example.demo.mapper.ProductMapper;
import com.example.demo.service.ProductService;
import org.springframework.stereotype.Service;@Service
publicclass ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements ProductService {@Overridepublic Product getProductById(Long id) {DynamicDataSourceContextHolder.setDataSourceType("slave");try {return getById(id);} finally {DynamicDataSourceContextHolder.clearDataSourceType();}}@Overridepublic boolean saveProduct(Product product) {DynamicDataSourceContextHolder.setDataSourceType("master");try {return save(product);} finally {DynamicDataSourceContextHolder.clearDataSourceType();}}
}
Controller
package com.example.demo.controller;import com.example.demo.entity.Product;
import com.example.demo.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/products")
publicclass ProductController {@Autowiredprivate ProductService productService;@GetMapping("/{id}")public Product getProduct(@PathVariable Long id) {return productService.getProductById(id);}@PostMapping("/")public boolean addProduct(@RequestBody Product product) {return productService.saveProduct(product);}
}
Application
package com.example.demo;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
@MapperScan("com.example.demo.mapper")
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}
}
测试
添加产品
curl -X POST http://localhost:8080/products/ \-H "Content-Type: application/json" \-d '{"name": "Sample Product", "price": 99.99}'
Respons
true
获取产品
curl -X GET http://localhost:8080/products/1
Respons
{"id":1,"name":"Sample Product","price":99.99}