在Mysql上手写数据库分库,技术栈:Java、JDBC、反射
分库的重点是要制定出合适的分库规则,以及数据操作时,需要使用对应的路由规则。
例如:
案例中分库规则:根据3取余,拆分为3个数据库(后缀0,1,2)
<dependencies>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-coreartifactId>
<version>3.0.0.RELEASEversion>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>org.springframework.contextartifactId>
<version>3.0.0.RELEASEversion>
dependency>
<dependency>
<groupId>com.google.code.gsongroupId>
<artifactId>gsonartifactId>
<version>2.10version>
dependency>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>4.0.1version>
<scope>providedscope>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.30version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-war-pluginartifactId>
<version>3.3.2version>
plugin>
plugins>
build>
/***
* @title DbRouter
* @desctption
* @author Administrator
* @create 2023/6/16 14:23
**/
public class DbRouter {
public static final String BASE_DARASOURCE_FIX = "cms_spring_db";
public static final String DARASOURCE_FIX = BASE_DARASOURCE_FIX + "_";
public static final int BASE_NUM = 3;
/**
* 根据code取余
* @return
*/
public static String getDBRouter(Long code) {
int dataSourceNum = (int) (code % BASE_NUM);
return DARASOURCE_FIX + dataSourceNum;
}
/**
* 根据code取余
* @return
*/
public static String getDBRouter(Integer code) {
int dataSourceNum = code % BASE_NUM;
return DARASOURCE_FIX + dataSourceNum;
}
/**
* 根据code取余
* @return
*/
public static String getDBRouter(Double code) {
int dataSourceNum = code.intValue() % BASE_NUM;
return DARASOURCE_FIX + dataSourceNum;
}
/**
* 根据code取余
* @return
*/
public static String getDBRouter(Float code) {
int dataSourceNum = code.intValue() % BASE_NUM;
return DARASOURCE_FIX + dataSourceNum;
}
}
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
/**
* 数据库交互工具
*/
public class DaoUtil {
/**
* 通过类获取类名 转sql格式(包含前后缀` `)
* @param clazz
* @return
*/
public static String HumpToSQL(Class clazz){
String simpleName = clazz.getSimpleName();
String str = "`";
for (int i = 0; i < simpleName.length(); i++) {
char c = simpleName.charAt(i);
if (i == 0 && Character.isUpperCase(c)){
c = Character.toLowerCase(c);
}
if ( i != 0 && Character.isUpperCase(c) ){
str += "_" + Character.toLowerCase(c);
}else {
str += c;
}
}
str += "`";
return str;
}
/**
* String转sql格式
* @param simpleName
* @return
*/
public static String HumpToSQL(String simpleName){
String str = "";
for (int i = 0; i < simpleName.length(); i++) {
char c = simpleName.charAt(i);
if (i == 0 && Character.isUpperCase(c)){
c = Character.toLowerCase(c);
}
if ( i != 0 && Character.isUpperCase(c) ){
str += "_" + Character.toLowerCase(c);
}else {
str += c;
}
}
return str;
}
/**
* get方法名转对应sql字段名
* 例子:getTypeName -> type_name
*/
public static String getToSQL(String getMethodName){
String str = "";
String substring = getMethodName.substring(3);
for (int i = 0; i < substring.length(); i++) {
char c = substring.charAt(i);
if (i == 0){
str += Character.toLowerCase(c);
}else{
if (Character.isUpperCase(c)){
str += "_" + Character.toLowerCase(c);
}else {
str += c;
}
}
}
return str;
}
/**
* 数据库连接
* @param database
* @return
* @throws ClassNotFoundException
* @throws SQLException
*/
public static Connection getConnection(String database) throws ClassNotFoundException, SQLException {
//1.注册驱动
Class.forName("com.mysql.jdbc.Driver");
//2.获取数据库连接
database = "jdbc:mysql://127.0.0.1:3306/" + database;
Connection conn = DriverManager.getConnection(database, "root", "root");
return conn;
}
}
public interface BaseDAO {
//根据id查询单条数据
public Object queryInfo(Class clazz, Long id , String dataSource) throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException ;
//创建数据
public int createInfo(Class clazz, Object obj , String dataSource) throws SQLException, ClassNotFoundException, InvocationTargetException, IllegalAccessException;
}
public class BaseDAOImpl implements BaseDAO {
/**
* 根据id查询单条数据
* @param id
*/
@Override
public Object queryInfo(Class clazz, Long id , String dataSource) throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
//1.实例化自定义工具类
//2.获取连接
Connection conn = DaoUtil.getConnection(dataSource);
//3.创建Statement\PreparedStatement对象
PreparedStatement ps;
//4.初始化查询sql语句
String sql = "select * from " + DaoUtil.HumpToSQL(clazz) + " where " + clazz.getSimpleName().toLowerCase() + "_is_delete = 0 and "
+ clazz.getSimpleName().toLowerCase() + "_id = " + id;
//5.执行sql
ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
Field[] declaredFields = clazz.getDeclaredFields();
//6.处理结果
while (rs.next()){
//实例化新对象
Object obj = clazz.newInstance();
//给对象set值
for (Field declaredField : declaredFields) {
declaredField.setAccessible(true);
declaredField.set(obj,rs.getObject(DaoUtil.HumpToSQL(declaredField.getName())));
declaredField.setAccessible(false);
}
// 7、释放资源
conn.close();
ps.close();
return obj;
}
// 7、释放资源
conn.close();
ps.close();
return null;
}
/**
* Base创建数据
* @param clazz
* @param obj
*/
@Override
public int createInfo(Class clazz, Object obj , String dataSource) throws SQLException, ClassNotFoundException, InvocationTargetException, IllegalAccessException {
//1.实例化自定义工具类
//2.获取连接
Connection conn = DaoUtil.getConnection(dataSource);
//3.创建Statement\PreparedStatement对象
PreparedStatement ps;
//4.初始化查询sql语句
String sql = "insert into " + DaoUtil.HumpToSQL(clazz);
//6.字符串构造字段--完善sql语句
Field[] declaredFields = clazz.getDeclaredFields();
StringBuffer stringFields = new StringBuffer(" (");
StringBuffer stringSeats= new StringBuffer(" (");
//数据库字段集合
List<String> fildsList = new ArrayList<>();
//类属性集合
List<String> attributesList = new ArrayList<>();
for (Field declaredField : declaredFields) {
if ( !declaredField.getName().contains("UpdateTime")
&& !declaredField.getName().contains("IsDelete") && !declaredField.getName().equals("newsViews")){
//如果是不用设置的属性就加入集合
fildsList.add(DaoUtil.HumpToSQL(declaredField.getName()));
attributesList.add(declaredField.getName());
}
}
// System.out.println("字段集合:" + fildsList);
for (int i = 0; i < fildsList.size(); i++) {
stringFields.append(fildsList.get(i));
stringSeats.append("?");
if (i != fildsList.size() - 1){
//如果不是最后一个
stringFields.append(",");
stringSeats.append(",");
}else {
//最后一个
stringFields.append(") ");
stringSeats.append(") ");
}
}
sql += stringFields + " value " + stringSeats;
// System.out.println("sql语句:" + sql);
//填充占位符
ps = conn.prepareStatement(sql);
Method[] methods = clazz.getMethods();
//初始化n n用来标志占位符位置
int n = 0;
for (Method method : methods) {
//获取符合的get方法 枚举排除掉不用插入的属性(排除数据库部分自动生成的字段默认值)
if ( method.getName().contains("get") && !method.getName().contains("UpdateTime")
&& !method.getName().contains("IsDelete") && !method.getName().equals("getClass") && !method.getName().equals("getNewsViews")) {
//遍历属性集合,通过找到get方法对应上的属性,更新占位符索引
for (int i = 0; i < attributesList.size(); i++) {
if (method.getName().toLowerCase().contains(attributesList.get(i).toLowerCase())){
n = i + 1;
}
}
//用get方法获取对象的属性值
method.setAccessible(true);
Object date = method.invoke(obj);
//把获取到的值填充到sql的占位符
ps.setObject(n,date);
// System.out.println(method.getName() + " :" + date + " 占位符位置" + n);
method.setAccessible(false);
}
}
int i = ps.executeUpdate();
//7、释放资源
conn.close();
ps.close();
return i;
}
}
List<News> list = baseDAO.queryInfo(News.class);
for (News news : list) {
Long newsId = news.getNewsId();
String dataSource = DbRouter.getDBRouter(newsId) ;
System.out.println("newsId:" +newsId + " , 所属数据源:" + dataSource );
baseDAO.createInfo(News.class, news ,dataSource);
}*
System.out.println(list.size());
Long newsId = 1007L;
BaseDAO baseDAO = new BaseDAOImpl();
String dataSource = DbRouter.getDBRouter(newsId) ;
System.out.println("数据源路由地址:" + dataSource);
News news = (News) baseDAO.queryInfo(News.class ,newsId , dataSource);
System.out.println( new Gson().toJson(news));
通过上述实验,可以看出分库核心是分库/路由规则,基于此我们可以慢慢完善开发出分库分表组件,如开源框架:ShardingSphere-JDBC。