import java.sql.Connection;
import java.sql.ResultSet;
import java.util.Properties;
import com.mysql.jdbc.ReplicationDriver;
public class ReplicationDriverDemo {
public static void main(String[] args) throws Exception {
ReplicationDriver driver = new ReplicationDriver();
Properties props = new Properties();
// We want this for failover on the slaves
props.put("autoReconnect", "true");
// We want to load balance between the slaves
props.put("roundRobinLoadBalance", "true");
props.put("user", "foo");
props.put("password", "bar");
//
// Looks like a normal MySQL JDBC url, with a
// comma-separated list of hosts, the first
// being the 'master', the rest being any number
// of slaves that the driver will load balance against
//
Connection conn =
driver.connect("jdbc:mysql:replication://master,slave1,slave2,slave3/test",
props);
//
// Perform read/write work on the master
// by setting the read-only flag to "false"
//
conn.setReadOnly(false);
conn.setAutoCommit(false);
conn.createStatement().executeUpdate("UPDATE some_table ....");
conn.commit();
//
// Now, do a query from a slave, the driver automatically picks one
// from the list
//
conn.setReadOnly(true);
ResultSet rs =
conn.createStatement().executeQuery("SELECT a,b FROM alt_table");
.......
}
}
MySQL 提供支持读写分离的驱动类:
com.mysql.jdbc.ReplicationDriver
替代
com.mysql.jdbc.Driver
注意,所有参数主从统一:
jdbc:mysql:replication://<master>,<slave>.../...?...=...
当然,用户名和密码也必须相同
触发Slave的情况
-
设置 auto_commit = false
-
设置 readOnly 为 true
综上特点,读写分离依赖于事务
常用使用场景:
第一种, 事务管理使用【注解】支持
通常,事务管理在Service层,只需要简单的操作即可支持读写分离:
1 2 |
|
事务开启后,查询自动切换到从库。
注意:@Transactional 默认的readOnly参数是false,更新操作不需要特别的改动。propagation是指的事务传播方式,默认设置是Require,指的是“本次操作需要事务支持,如果没有事务开启一个事务,如果有事务,加入到该事务中”
考虑复杂一点的情况,当Service中出现自我方法的调用时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
当外部调用getOrder时,getOrder方法的@Transaction注解生效,设置从库查询。
当外部调用updateOrder时,updateOrder方法的@Transaction注解生效,设置操作主库。
注意,这两个方法都调用了findSubOrderList方法,而调用的对象是this,不是被spring事务管理器替换过的service对象,所以findSubOrderList方法上的@Transaction注解无效,会根据上文环境来查主库和从库
这种特性对于业务来说是恰当好处的,生效的事务是在最外层的方法上,可以避免在一个事务内部出现读写库不统一的情况。
更复杂一点的情况,当service中调用了其它类的service:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
1, 当外部调用OrderSerivce的getOrder时,getOrder方法的@Transaction注解生效,设置从库查询。
getOrder内部调用了OrderCouponService的getById方法,由于orderCouponService是spring提供的对象,经过了事务管理,所以getById方法上的@Transaction注解生效,
我们知道Require这个事务传播的特性,getById不会创建新的事务,所以依旧是由从库读取数据。
2, 当外部调用OrderSerivce的saveOrder时,saveOrder方法的@Transaction注解生效,设置操作主库。
saveOrder内部调用了OrderCouponService的saveCoupon方法,同样由于Require的特性,没有创建新事务,操作主库。
3, 当外部调用OrderSerivce的updateOrder时,updateOrder方法的@Transaction注解生效,设置操作主库。
updateOrder内部调用了OrderCouponService的getById方法,同样由于Require的特性,没有创建新事务,从主库读出数据。
这些特性也是很好的,我们只需要关心最外部调用的方法的注解内容,就可以确定走的哪个库。
更复杂点的情况是新开事务的情况,建议谨慎对待
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
该想法是构想把OrderSerivce的getOrderPrice查询走从库,其中一个小逻辑更新库设置操作主库。在没有设置主从的情况下,这种方式是支持的,并不会出现问题。
但在设置了主从的情况下,这种业务逻辑操作就“不安全”了,因为,updateById走的是主库,它的更新操作是依赖于主从同步的,很有可能getById取到了“过期”的数据。
这种情况在业务上来说是应该要避免的,如果不能避免,最好的办法是让外部都走主库,保证数据来源的一致性。
综上,事务管理配置用注解的方式还是蛮方便的。
第二种, 事务管理使用【XML配置】支持
XML配置的事务是以判断指定名称开头的方法来实现的,跟注解配置事务是类似的。可以把select和get判定为readOnly,传播机制设定为Require。
第三种,使用支持读事务的入口类
鉴于现有代码Service层被融合到web和admin中,在Service层的注入会影响多个系统,而单独写方法,不免繁琐,使用代理方法支持事务的读比较灵活。
流程:
这个模式好处在于可以根据业务的需要,合理安排开发和测试的工作,影响范围可控。
实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
|
按需配置:
1 2 3 4 5 |
|
使用时请注意:原有@Autowired方式注入请改成@Resource指定名称的方式,以区别不同入口
1 2 3 4 5 |
|