前记:大家带着挑剔的眼光,多多批判和指正!🙏
在ERP项目中实现读写分离,我们可以使用Java结合Spring框架和MyBatis ORM来实现。以下是一个简化的例子,展示了如何在ERP项目中配置和使用读写分离。
一、项目结构
假设我们的ERP项目使用Maven构建,项目结构大致如下:
erp-project
├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ └── example
│ │ │ ├── config
│ │ │ │ └── DataSourceConfig.java
│ │ │ ├── mapper
│ │ │ │ └── ProductMapper.java
│ │ │ ├── model
│ │ │ │ └── Product.java
│ │ │ ├── service
│ │ │ │ └── ProductService.java
│ │ │ └── service/impl
│ │ │ └── ProductServiceImpl.java
│ │ └── resources
│ │ ├── application.yml
│ │ └── mapper
│ │ └── ProductMapper.xml
│ └── test
│ └── java
│ └── com
│ └── example
│ └── ErpProjectApplicationTests.java
├── pom.xml
└── README.md
二、配置数据源
在application.yml
中配置主从数据源:
spring:
datasource:
master:
url: jdbc:mysql://localhost:3306/erp_master
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
slave:
url: jdbc:mysql://localhost:3306/erp_slave
username: root
password: root
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.example.model
三、创建数据源配置类
在com.example.config
包下创建DataSourceConfig.java
,用于配置数据源和动态数据源路由:
// 省略了部分代码,包括导入语句和具体实现细节
@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();
}
@Primary
@Bean(name = "dataSource")
public DataSource routingDataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slaveDataSource") DataSource slaveDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceType.MASTER, masterDataSource);
targetDataSources.put(DataSourceType.SLAVE, slaveDataSource);
DynamicRoutingDataSource dataSource = new DynamicRoutingDataSource();
dataSource.setDefaultTargetDataSource(masterDataSource);
dataSource.setTargetDataSources(targetDataSources);
return dataSource;
}
// 其他配置和动态数据源路由逻辑...
}
四、创建模型类
在com.example.model
包下创建Product.java
:
// 省略了导入语句
public class Product {
private Long id;
private String name;
private Double price;
// getters and setters...
}
五、创建Mapper接口和XML映射文件
在com.example.mapper
包下创建ProductMapper.java
:
// 省略了导入语句
@Mapper
public interface ProductMapper {
@Select("SELECT * FROM product WHERE id = #{id}")
Product findById(Long id);
@Insert("INSERT INTO product(name, price) VALUES(#{name}, #{price})")
@Options(useGeneratedKeys = true, keyProperty = "id")
void insert(Product product);
// 其他CRUD方法...
}
在resources/mapper
目录下创建ProductMapper.xml
(如果使用了XML配置):
<!-- 省略了XML声明和DOCTYPE声明 -->
<mapper namespace="com.example.mapper.ProductMapper">
<!-- SQL语句... -->
</mapper>
注意:在这个例子中,我们实际上没有使用XML映射文件,因为所有的SQL语句都直接在Mapper接口中使用了注解。如果你更喜欢使用XML配置,可以将SQL语句移到ProductMapper.xml
中。
六、创建服务类
在com.example.service
和com.example.service.impl
包下创建服务接口和实现类:
// ProductService.java
public interface ProductService {
Product findById(Long id);
void createProduct(Product product);
// 其他服务方法...
}
// ProductServiceImpl.java
@Service
public class ProductServiceImpl implements ProductService {
@Autowired
private ProductMapper productMapper;
@Override
@DataSource(DataSourceType.SLAVE) // 读取时使用从库
public Product findById(Long id) {
return productMapper.findById(id);
}
@Override
@DataSource(DataSourceType.MASTER) // 写入时使用主库
public void createProduct(Product product) {
productMapper.insert(product);
}
// 其他服务方法实现...
}
注意:@DataSource
注解是一个自定义注解,用于标记方法应该使用哪个数据源。你需要自己实现这个注解和它的处理逻辑(通常通过AOP)。
七、自定义注解和AOP处理
创建DataSource
注解和AOP切面来处理数据源切换:
// DataSource.java
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
DataSourceType value() default DataSourceType.MASTER;
}
// DataSourceAspect.java
@Aspect
@Component
public class DataSourceAspect {
@Before("@annotation(dataSource)")
public void changeDataSource(JoinPoint point, DataSource dataSource) {
DataSourceContextHolder.setDataSourceType(dataSource.value());
}
@After("@annotation(dataSource)")
public void clearDataSource(JoinPoint point, DataSource dataSource) {
DataSourceContextHolder.clearDataSourceType();
}
}
你还需要实现DataSourceContextHolder
和DataSourceType
枚举类来存储和获取当前线程的数据源类型。
八、测试
编写单元测试或启动应用程序,通过调用ProductService
的方法来验证读写分离是否按预期工作。
九、注意事项
- 确保主从数据库之间的数据同步是及时的。
- 在高并发场景下,需要特别注意数据源切换的线程安全性。
- 根据业务需求,可能需要更复杂的读写分离策略,比如基于负载均衡的读操作分发。
这个例子提供了一个基本的框架,你可以根据实际需求进行扩展和修改。
(望各位潘安、各位子健不吝赐教!多多指正!🙏)