通过MyCat实现读写分离的需求,环境为springboot下的JPA。
MyCat版本为1.6
1.MySQL主从同步配置
2.下载安装MyCat
安装myCat
// 进入local文件夹
cd /usr/local
// 下载mycat安装包
wget http://dl.mycat.org.cn/1.6.7.5/2020-3-3/Mycat-server-1.6.7.5-test-20200303154735-linux.tar.gz
//解压
tar -zxvf Mycat-server-1.6.7.5-test-20200303154735-linux.tar.gz
cd mycat
useradd mycat
chown -R mycat:mycat /usr/local/mycat
添加环境变量
$ vim /etc/profile
// 在最后添加
MYCAT_HOME=/usr/local/mycat/mycat
PATH=$MYCAT_HOME/bin:$PATH
export PATH
// 使配置生效
$ source /etc/profile
mycat的启停命令
//查看状态
mycat status
// 启动
mycat start
// 关闭
mycat stop
3.myCat的配置
- server.xml配置
这里是myCat的整体大面的配置,包含端口以及用户的配置。
拉到最下面,来看用户的配置。
// 默认提供了2个示例,一个读写默认用户,一个只读用户。
// 我们只需要用到一个主用户
// 配置mycat的账户、密码,springboot中的数据源就使用这里的账户密码
// 配置schemas,这是myCat的读写分离、分片规则等的主要配置,
// 这里的schemas只配置规则的ID,具体的规则是在另一个配置文件中
<user name="root" defaultAccount="true">
<property name="password">root</property>
<property name="schemas">demo_db</property>
<property name="defaultSchema">demo_db</property>
</user>
-
schema.xml的配置
<?xml version="1.0"?>
<!DOCTYPE mycat:schema SYSTEM "schema.dtd">
<mycat:schema xmlns:mycat="http://io.mycat/">
<schema name="demo_db" checkSQLschema="false" sqlMaxLimit="100" dataNode="dn1" >
</schema>
<dataNode name="dn1" dataHost="dataHost220" database="demo_db" />
<dataHost name="dataHost220" maxCon="1000" minCon="10" balance="3"
writeType="0" dbType="mysql" dbDriver="native" switchType="1" slaveThreshold="100">
<heartbeat>select user()</heartbeat>
<writeHost host="hostM1" url="192.168.3.220:3306" user="root" password="root">
<readHost host="hostS2" url="192.168.3.220:3307" user="root" password="root" />
</writeHost>
</dataHost>
</mycat:schema>
4.检查mycat是否配置完成
启动
mycat start
如果启动过程中,遇到问题,可以查看日志,在/usr/local/mycat/logs中的wrapper.log与mycat.log中
shcema的名字,最好和数据库的名字保持一致,包括大小写
测试:
在mycat中修改字段,查看主从库,都有改动。
在从库中修改字段,查看主库未改变,mycat有改变。
5.springboot配置
- 关闭JPA的自动事务
JPA有自动事务,所有的更新语句、查询语句都有事务,这涉及到JPA的缓存机制,可以自己去了解。
任何带有事务的sql,mycat都会去走主库,而不是从库。
因此JPA的自动事务必须关闭。
在启动类上加如下注释,可以关闭JPA的默认事务
@SpringBootApplication
@EnableJpaRepositories(enableDefaultTransactions = false)
public class MydemoApplication {
public static void main(String[] args) {
SpringApplication.run(MydemoApplication.class, args);
}
}
- JPA的事务配置
JPA中的更新操作,必须有事务才能执行,因此我们需要给每一个更新操作加上事务。
方法一:
手动加事务,注解@Transactional,
在service类中的每一个更新方法上,加事务注解
方法二:
通过配置类,统一添加事务,
com.zyy.*.service.*.*(..)表达式的意思是,com.zyy下的任意包中的service包中的任意类的任意方法,方法参数为任意个
@Aspect
@Configuration
public class TransactionalConfigForBoot {
private static final String AOP_POINTCUT_EXPRESSION = "execution(* com.zyy.*.service.*.*(..))";
@Autowired
private PlatformTransactionManager transactionManager;
@Bean
public TransactionInterceptor txAdvice() {
DefaultTransactionAttribute txAttr_REQUIRED = new DefaultTransactionAttribute();
txAttr_REQUIRED.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
DefaultTransactionAttribute txAttr_REQUIRED_READONLY = new DefaultTransactionAttribute();
txAttr_REQUIRED_READONLY.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
txAttr_REQUIRED_READONLY.setReadOnly(true);
NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
source.addTransactionalMethod("save*", txAttr_REQUIRED);
source.addTransactionalMethod("add*", txAttr_REQUIRED);
source.addTransactionalMethod("create*", txAttr_REQUIRED);
source.addTransactionalMethod("update*", txAttr_REQUIRED);
source.addTransactionalMethod("delete*", txAttr_REQUIRED);
source.addTransactionalMethod("exec*", txAttr_REQUIRED);
source.addTransactionalMethod("set*", txAttr_REQUIRED);
source.addTransactionalMethod("get*", txAttr_REQUIRED_READONLY);
source.addTransactionalMethod("query*", txAttr_REQUIRED_READONLY);
source.addTransactionalMethod("find*", txAttr_REQUIRED_READONLY);
source.addTransactionalMethod("list*", txAttr_REQUIRED_READONLY);
source.addTransactionalMethod("count*", txAttr_REQUIRED_READONLY);
source.addTransactionalMethod("is*", txAttr_REQUIRED_READONLY);
return new TransactionInterceptor(transactionManager, source);
}
@Bean
public Advisor txAdviceAdvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
return new DefaultPointcutAdvisor(pointcut, txAdvice());
}
}