博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
支持分表的ORM框架实现
阅读量:4080 次
发布时间:2019-05-25

本文共 8643 字,大约阅读时间需要 28 分钟。

本文带大家实现一个支持分表的ORM框架,通过简单代码实现来理解核心原理

惯例贴出GitHub地址:https://github.com/whiteBX/worm

首先明确一个我们要实现的效果,然后再考虑实现,下面以user表作为例子:

@Table("user_")@SplitKey(column = "id", tableNum = 8)public class UserDO {
@Column(value = "id", columnType = JDBCType.BIGINT) private Long id; @Column(value = "user_name", columnType = JDBCType.VARCHAR) private String userName; @Column(value = "real_name", columnType = JDBCType.VARCHAR) private String realName; @Column(value = "phone", columnType = JDBCType.VARCHAR) private String phone; @Column(value = "sex", columnType = JDBCType.TINYINT) private short sex; // 省略getter/setter}

我们想要实现的就是在UserDO上加上自定义的Table、SplitKey、Column注解,之后就可以通过操作对象的方式来实现对于用户表的增删改查,并且能支持根据SplitKey中配置的列和表数量来自动实现分表操作。

首先来定义之前用到的三个注解:

@Target(ElementType.FIELD)@Retention(RetentionPolicy.RUNTIME)public @interface Column {
String value(); JDBCType columnType();}@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public @interface SplitKey {
/** * 分库分表的列,其类型必须为Long */ String column(); /** * 分表数量 */ int tableNum();}@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface Table {
String value();}

接下来是如何使用这些注解,这里我们用一个ReflectUtil来封装出从类中解析表名、分表规则、列名等操作:

public class ReflectUtil {
/** * 解析表名 */ public static String parseTable(Class clazz) {
AssertUtil.isTrue(clazz.isAnnotationPresent(Table.class), ErrorCode.ACCESS_ERROR, "class need annotation Table"); Table table = (Table) clazz.getDeclaredAnnotation(Table.class); return table.value(); } /** * 解析分表规则 */ public static SplitKey parseSplitKey(Class clazz) {
AssertUtil.isTrue(clazz.isAnnotationPresent(SplitKey.class), ErrorCode.ACCESS_ERROR, "class need annotation SplitKey"); return (SplitKey) clazz.getDeclaredAnnotation(SplitKey.class); } /** * 解析列 */ public static Column parseColumn(Class clazz, String fieldName) {
try {
return clazz.getDeclaredField(fieldName).getDeclaredAnnotation(Column.class); } catch (NoSuchFieldException e) {
throw new BizException(ErrorCode.ACCESS_ERROR, "parse Column error"); } } /** * 反射获取对象的属性 */ public static Object getFieldValue(Object obj, String fieldName) {
Field field = null; boolean access = false; try {
field = obj.getClass().getDeclaredField(fieldName); access = field.isAccessible(); field.setAccessible(true); return field.get(obj); } catch (Exception e) {
throw new BizException(ErrorCode.ACCESS_ERROR, "get Field value error: " + e.getMessage()); } finally {
field.setAccessible(access); } } /** * 解析获取tableMeta */ public static TableMeta parseMeta(Class clazz) {
TableMeta meta = new TableMeta(); meta.setTableName(parseTable(clazz)); meta.setTableClass(clazz); meta.setSplitKey(parseSplitKey(clazz)); for (Field field : clazz.getDeclaredFields()) {
Column column = parseColumn(clazz, field.getName()); meta.addColumnMeta(field.getName(), column.value(), column.columnType()); } return meta; }}

上面工具类中引入了一个TableMeta类,这个类是我们用来缓存所有标注了Table注解的类的信息的,方便后面直接使用,也比每次去对表增删改查时新反射解析一次效率更高,其代码如下:

public class TableMeta {
/** * 表名 */ private String tableName; /** * 对应实体类型 */ private Class tableClass; /** * 分库分表 */ private SplitKey splitKey; /** * 列属性 */ private List
columnMetaList; public class ColumnMeta {
/** * 字段名 */ private String field; /** * 列名 */ private String column; /** * 列类型 */ private JDBCType jdbcType; public ColumnMeta(String field, String column, JDBCType jdbcType) {
this.field = field; this.column = column; this.jdbcType = jdbcType; } } public ColumnMeta getColumnMetaByColumn(String columnName) {
for (ColumnMeta columnMeta : columnMetaList) {
if (columnMeta.equals(columnMeta.column)) {
return columnMeta; } } return null; } public ColumnMeta getColumnMetaByField(String field) {
for (ColumnMeta columnMeta : columnMetaList) {
if (field.equals(columnMeta.field)) {
return columnMeta; } } return null; } public void addColumnMeta(String field, String column, JDBCType jdbcType) {
if (this.columnMetaList == null) {
columnMetaList = new ArrayList<>(); } columnMetaList.add(new ColumnMeta(field, column, jdbcType)); }//省略getter/setter}

通过上面我们已经可以获取到表名、列这些需要的属性了,接下来来获取分表情况下实际的表名:

public class DefaultSplitStrategy {
public static String getRealTableName(Object o) {
TableMeta tableMeta = OrmHolder.get(o.getClass()); AssertUtil.notNull(tableMeta, ErrorCode.ACCESS_ERROR, ""); // not split if (tableMeta.getSplitKey() == null) {
return tableMeta.getTableName(); } SplitKey splitKey = tableMeta.getSplitKey(); Object splitValue = ReflectUtil.getFieldValue(o, splitKey.column()); AssertUtil.isTrue(splitValue instanceof Long, ErrorCode.ACCESS_ERROR, "split key must be long: " + splitKey.column()); return tableMeta.getTableName() + (Long) splitValue % splitKey.tableNum(); }}

这里默认实现采用了取余的方式,如果需要更复杂的分表方式可以自行扩充。

到这一步已经拿到了数据库的所有信息了,包括:实际的表名、列名、列属性、列的值。接下来要做的就是根据增删该查来把SQL拼出来即可:

public class SqlUtil {
public static String buildInsertSQL(String tableName, Object obj) {
StringBuilder sql = new StringBuilder(); StringBuilder valueStr = new StringBuilder(); sql.append("Insert into ").append(tableName).append(" ("); valueStr.append("VALUES ("); List
columnMetaList = OrmHolder.get(obj.getClass()).getColumnMetaList(); for (TableMeta.ColumnMeta columnMeta : columnMetaList) {
sql.append(columnMeta.getColumn()).append(","); valueStr.append("'").append(ReflectUtil.getFieldValue(obj, columnMeta.getField())).append("',"); } sql.deleteCharAt(sql.lastIndexOf(",")); valueStr.deleteCharAt(valueStr.lastIndexOf(",")); valueStr.append(")"); sql.append(") ").append(valueStr); return sql.toString(); }}

这里只实现了拼装insert的sql,删改查原理差不多,大家自行拼一下即可。最后通过继承JdbcTemplate来实现一个我们自己的JdbcTemplate把所需要的东西组装起来就可以了:

public class WJdbcTemplate extends JdbcTemplate implements JdbcOperation {
public WJdbcTemplate(DataSource dataSource) {
super(dataSource); } public int insert(Object obj) {
return this.update(SqlUtil.buildInsertSQL(DefaultSplitStrategy.getRealTableName(obj), obj)); }}

下面是测试代码:

@Configuration@PropertySource("classpath:applications.properties")public class BeanConfig {
@Bean @ConfigurationProperties() public DataSourceProperties dataSourceProperties() {
return new DataSourceProperties(); } @Bean(name = "dataSource") public DataSource dataSource(DataSourceProperties dataSourceProperties) {
return DataSourceBuilder.create(dataSourceProperties.getClass().getClassLoader()) .type(HikariDataSource.class) .driverClassName(dataSourceProperties.getDriverClassName()) .url(dataSourceProperties.getJdbcUrl()) .username(dataSourceProperties.getUser()) .password(dataSourceProperties.getPassword()) .build(); } @Bean(name = "wJdbcTemplate") public JdbcTemplate jdbcTemplate( @Qualifier("dataSource") DataSource dataSource) {
return new WJdbcTemplate(dataSource); }}@RunWith(SpringRunner.class)@SpringBootApplication@ComponentScan("org.white.worm")@SpringBootTestpublic class TestOrm {
@Resource private WJdbcTemplate wJdbcTemplate; @Test public void testInsert() {
UserDO userDO = new UserDO(); int count = 0; for (long i = 0; i < 1000; i++) {
userDO.setId(i); userDO.setUserName("white" + i); userDO.setRealName("白" + i); userDO.setPhone("13100000000"); userDO.setSex((short) (i % 2)); count += wJdbcTemplate.insert(userDO); } System.out.println(count); }}

启动mysql并创建好user_0 ~ user_7之后,运行TestOrm中的testInsert方法,我们发现数据库都按主键取余的方式插入了对应的表中。

至此我们一个自定义的支持分表的ORM框架就完成了!

转载地址:http://rhsni.baihongyu.com/

你可能感兴趣的文章
[LeetCode By Python]121. Best Time to Buy and Sell Stock
查看>>
Android2.1消息应用(Messaging)源码学习笔记
查看>>
剑指offer算法题分析与整理(三)
查看>>
JVM并发机制探讨—内存模型、内存可见性和指令重排序
查看>>
nginx+tomcat+memcached (msm)实现 session同步复制
查看>>
WAV文件解析
查看>>
WPF中PATH使用AI导出SVG的方法
查看>>
QT打开项目提示no valid settings file could be found
查看>>
java LinkedList与ArrayList迭代器遍历和for遍历对比
查看>>
coursesa课程 Python 3 programming 统计文件有多少单词
查看>>
多线程使用随机函数需要注意的一点
查看>>
getpeername,getsockname
查看>>
所谓的进步和提升,就是完成认知升级
查看>>
如何用好碎片化时间,让思维更有效率?
查看>>
No.182 - LeetCode1325 - C指针的魅力
查看>>
Encoding Schemes
查看>>
带WiringPi库的交叉笔译如何处理二之软链接概念
查看>>
Java8 HashMap集合解析
查看>>
自定义 select 下拉框 多选插件
查看>>
Linux常用统计命令之wc
查看>>