`
blue2048
  • 浏览: 183066 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

深入浅出Mybatis-sql自动生成

阅读更多

转自 http://blog.csdn.net/hupanfeng/article/details/9320665

 

本文提供了一种自动生成sql语句的方法,它针对的对象是有主键或唯一索引的单表,提供的操作有增、删、改、查4种。理解本文和本文的提供的代码需要有java注解的知识,因为本文是基于注解生成sql的。本文适配的mybatis版本是3.2.2

准备

为什么在StatementHandler拦截

深入浅出MyBatis-Sqlsession章节介绍了一次sqlsession的完整执行过程,从中可以知道sql的解析是在StatementHandler里完成的,所以为了自动生成sql需要拦截StatementHandler

MetaObject简介

在我的实现里大量使用了MetaObject这个对象,因此有必要先介绍下它。MetaObjectMybatis提供的一个的工具类,通过它包装一个对象后可以获取或设置该对象的原本不可访问的属性(比如那些私有属性)。它有个三个重要方法经常用到:

1)       MetaObject forObject(Object object,ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory)

2)       Object getValue(String name)

3)       void setValue(String name, Object value)

方法1)用于包装对象;方法2)用于获取属性的值(支持OGNL的方法);方法3)用于设置属性的值(支持OGNL的方法);

插件的原理

参见深入浅出Mybatis-插件原理

 

有了上面这些基础知识的准备后,就可以我们的主题了。

拦截器签名

[java] view plaincopy
 
  1. @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class})})  
  2. public class AutoMapperInterceptor implements Interceptor {  
  3. ...  
  4. }  

从签名里可以看出,要拦截的目标类型是StatementHandler(注意:type只能配置成接口类型),拦截的方法是名称为prepare参数为Connection类型的方法。

intercept的实现

[java] view plaincopy
 
  1. @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class})})  
  2. public class AutoMapperInterceptor implements Interceptor {  
  3.     private static final Log logger = LogFactory.getLog(AutoMapperInterceptor.class);  
  4.     private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();  
  5.     private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();  
  6.   
  7.     @Override  
  8.     public Object intercept(Invocation invocation) throws Throwable {  
  9.         StatementHandler statementHandler = (StatementHandler) invocation.getTarget();  
  10.         MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY,  
  11.                 DEFAULT_OBJECT_WRAPPER_FACTORY);  
  12.         // 分离代理对象链  
  13.         while (metaStatementHandler.hasGetter("h")) {  
  14.             Object object = metaStatementHandler.getValue("h");  
  15.             metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);  
  16.         }  
  17.         // 分离最后一个代理对象的目标类  
  18.         while (metaStatementHandler.hasGetter("target")) {  
  19.             Object object = metaStatementHandler.getValue("target");  
  20.             metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);  
  21.         }  
  22.         String originalSql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");  
  23.         Configuration configuration = (Configuration) metaStatementHandler.getValue("delegate.configuration");  
  24.         Object parameterObject = metaStatementHandler.getValue("delegate.boundSql.parameterObject");  
  25.         if (null == originalSql || "".equals(originalSql)) {  
  26.             String newSql = "";  
  27.             MappedStatement mappedStatement = (MappedStatement) metaStatementHandler  
  28.                     .getValue("delegate.mappedStatement");  
  29.             // 根据ID生成相应类型的sql语句(id需剔除namespace信息)  
  30.             String id = mappedStatement.getId();  
  31.             id = id.substring(id.lastIndexOf(".") + 1);  
  32.             if ("insert".equals(id)) {  
  33.                 newSql = SqlBuilder.buildInsertSql(parameterObject);  
  34.             } else if ("update".equals(id)) {  
  35.                 newSql = SqlBuilder.buildUpdateSql(parameterObject);  
  36.             } else if ("delete".equals(id)) {  
  37.                 newSql = SqlBuilder.buildDeleteSql(parameterObject);  
  38.             } else if ("select".equals(id)) {  
  39.                 newSql = SqlBuilder.buildSelectSql(parameterObject);  
  40.             }  
  41.             logger.debug("Auto generated sql:" + newSql);  
  42.             //  
  43.             SqlSource sqlSource = buildSqlSource(configuration, newSql, parameterObject.getClass());  
  44.             List<ParameterMapping> parameterMappings = sqlSource.getBoundSql(parameterObject).getParameterMappings();  
  45.             metaStatementHandler.setValue("delegate.boundSql.sql", sqlSource.getBoundSql(parameterObject).getSql());  
  46.             metaStatementHandler.setValue("delegate.boundSql.parameterMappings", parameterMappings);  
  47.         }  
  48.         // 调用原始statementHandler的prepare方法  
  49.         statementHandler = (StatementHandler) metaStatementHandler.getOriginalObject();  
  50.         statementHandler.prepare((Connection) invocation.getArgs()[0]);  
  51.         // 传递给下一个拦截器处理  
  52.         return invocation.proceed();  
  53.     }  
  54.   
  55.     @Override  
  56.     public Object plugin(Object target) {  
  57.         if (target instanceof StatementHandler) {  
  58.             return Plugin.wrap(target, this);  
  59.         } else {  
  60.             return target;  
  61.         }  
  62.     }  
  63.   
  64.     @Override  
  65.     public void setProperties(Properties properties) {  
  66.   
  67.     }  
  68.   
  69.     private SqlSource buildSqlSource(Configuration configuration, String originalSql,   
  70.     Class<?> parameterType) {  
  71.         SqlSourceBuilder builder = new SqlSourceBuilder(configuration);  
  72.         return builder.parse(originalSql, parameterType, null);  
  73.     }  
  74. }  

StatementHandler的默认实现类是RoutingStatementHandler,因此拦截的实际对象是它。RoutingStatementHandler的主要功能是分发,它根据配置Statement类型创建真正执行数据库操作的StatementHandler,并将其保存到delegate属性里。由于delegate是一个私有属性并且没有提供访问它的方法,因此需要借助MetaObject的帮忙。通过MetaObject的封装后我们可以轻易的获得想要的属性。

在上面的方法里有个两个循环,通过他们可以分离出原始的RoutingStatementHandler(而不是代理对象)。

有了插件帮你生成sql语句后,mapper配置文件里单表的增删改查部分就不需要再配置sql代码了,但由于插件需要通过id来生成不同类型的sql语句,因此必要的配置还是需要的,而且相应的id必须是下面的这几个(区分大小写):

[html] view plaincopy
 
  1. <update id="update" parameterType="UserDto"></update>  
  2. <insert id="insert" parameterType="UserDto"></insert>  
  3. <delete id="delete" parameterType="UserDto"></delete>  
  4. <select id="select" parameterType="UserDto" resultType="UserDto""></select>  

SqlBuilder

SqlBuilder的相应方法接受一个dto对象作为参数,它们根据这个对象的属性值和配置的注解生成相应的sql。

[java] view plaincopy
 
  1. @TableMapperAnnotation(tableName = "t_user", uniqueKeyType = UniqueKeyType.Single, uniqueKey = " userid ")  
  2. public class UserDto {  
  3.     @FieldMapperAnnotation(dbFieldName = "userid", jdbcType = JdbcType.INTEGER)  
  4.     private Integer userid;  
  5.     @FieldMapperAnnotation(dbFieldName = "username", jdbcType = JdbcType.VARCHAR)  
  6.     private String username;  
  7.     ...  
  8. }  

这个对象包含了两种注解,一个是TableMapperAnnotation注解,它保存了表名、唯一键类型和构成唯一键的字段;另一个是FieldMapperAnnotation注解,它保存了数据库字段名和字段类型信息。这两个注解都是必须的。SqlBuilder生成sql时会用到他们,下面以生成insert语句的方法为例,其他方法类似:

[java] view plaincopy
 
  1. public static String buildInsertSql(Object object) throws Exception {  
  2.     if (null == object) {  
  3.         throw new RuntimeException("Sorry,I refuse to build sql for a null object!");  
  4.     }  
  5.     Map dtoFieldMap = PropertyUtils.describe(object);  
  6.     // 从参数对象里提取注解信息  
  7.     TableMapper tableMapper = buildTableMapper(object.getClass());  
  8.     // 从表注解里获取表名等信息  
  9.     TableMapperAnnotation tma = (TableMapperAnnotation) tableMapper.getTableMapperAnnotation();  
  10.     String tableName = tma.tableName();  
  11.     StringBuffer tableSql = new StringBuffer();  
  12.     StringBuffer valueSql = new StringBuffer();  
  13.   
  14.     tableSql.append("insert into ").append(tableName).append("(");  
  15.     valueSql.append("values(");  
  16.   
  17.     boolean allFieldNull = true;  
  18.     // 根据字段注解和属性值联合生成sql语句  
  19.     for (String dbFieldName : tableMapper.getFieldMapperCache().keySet()) {  
  20.         FieldMapper fieldMapper = tableMapper.getFieldMapperCache().get(dbFieldName);  
  21.         String fieldName = fieldMapper.getFieldName();  
  22.         Object value = dtoFieldMap.get(fieldName);  
  23.         // 由于要根据字段对象值是否为空来判断是否将字段加入到sql语句中,因此DTO对象的属性不能是简单类型,反而必须是封装类型  
  24.         if (value == null) {  
  25.             continue;  
  26.         }  
  27.         allFieldNull = false;  
  28.         tableSql.append(dbFieldName).append(",");  
  29.         valueSql.append("#{").append(fieldName).append(",").append("jdbcType=")  
  30.                 .append(fieldMapper.getJdbcType().toString()).append("},");  
  31.     }  
  32.     if (allFieldNull) {  
  33.         throw new RuntimeException("Are you joking? Object " + object.getClass().getName()  
  34.                 + "'s all fields are null, how can i build sql for it?!");  
  35.     }  
  36.     tableSql.delete(tableSql.lastIndexOf(","), tableSql.lastIndexOf(",") + 1);  
  37.     valueSql.delete(valueSql.lastIndexOf(","), valueSql.lastIndexOf(",") + 1);  
  38.     return tableSql.append(") ").append(valueSql).append(")").toString();  
  39. }  

plugin的实现

 

[java] view plaincopy
 
  1. public Object plugin(Object target) {  
  2.     // 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的  
  3.     // 次数  
  4.     if (target instanceof StatementHandler) {  
  5.         return Plugin.wrap(target, this);  
  6.     } else {  
  7.         return target;  
  8.     }  
  9. }  
分享到:
评论

相关推荐

    电子书-深入浅出Mybatis技术原理与实战

    《深入浅出Mybatis技术原理与实战》这本书是针对Mybatis这一流行持久层框架的一份详尽指南。Mybatis是一个优秀的Java ORM(对象关系映射)框架,它允许开发者将数据库操作与应用程序逻辑分离,提高了代码的可读性...

    深入浅出MyBatis技术原理与实战

    《深入浅出MyBatis技术原理与实战》这本书聚焦于Java开发中的MyBatis框架,旨在帮助读者全面理解和掌握这一流行的数据持久层解决方案。MyBatis是一个轻量级的ORM(对象关系映射)框架,它允许开发者用SQL语句进行...

    深入浅出Mybatis技术原理与实战-高清pdf

    《深入浅出Mybatis技术原理与实战》这本书旨在帮助读者全面理解MyBatis的内部工作机制,并通过实例演练提升实际开发技能。 首先,我们要了解MyBatis的核心概念。MyBatis是一个基于Java的持久层框架,它简化了数据库...

    深入浅出MyBatis技术原理与实战 源代码

    "深入浅出MyBatis技术原理与实战"这本书旨在帮助读者理解MyBatis的核心概念,并通过实战演练提升其在实际项目中的应用能力。第二章的源代码主要涵盖了MyBatis的基本配置、动态SQL以及Mapper接口的使用等关键知识点。...

    深入浅出MyBatis技术原理与实战(高清目录+部份源码)

    《深入浅出MyBatis技术原理与实战》是一本针对Java开发者深度解析MyBatis框架的专业书籍。MyBatis是一个优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射,旨在简化Java开发中的数据库操作。这本书通过高清...

    深入浅出MyBatis技术原理与实战.杨开振(详细书签)

    总的来说,《深入浅出MyBatis技术原理与实战》会深入讲解MyBatis的各个方面,包括基本原理、配置、SQL映射、结果映射、事务管理、动态SQL、缓存机制以及实战应用。通过阅读本书,读者不仅可以了解MyBatis的全貌,还...

    MyBatis 深入浅出-动态SQL

    ### MyBatis深入浅出-动态SQL #### 动态SQL概述 在处理数据库操作时,根据不同的业务逻辑拼接SQL语句是一项繁琐且容易出错的任务。这不仅增加了代码的复杂度,还可能导致SQL语法错误。为了克服这些问题,MyBatis...

    深入浅出 mybatis技术原理与实践pdf

    《深入浅出 MyBatis 技术原理与实践》是一本专为 Java 开发者深入理解 MyBatis 框架而编写的书籍。MyBatis 是一款深受开发者喜爱的持久层框架,它允许程序员将 SQL 语句与 Java 代码紧密集成,从而简化了数据访问层...

    深入浅出MyBatis技术原理与实战 第六章 源代码

    在SQL执行流程中,当执行Mapper接口的方法时,MyBatis会通过代理对象找到对应的XML或注解中的SQL语句,并根据传递的参数动态生成SQL。这包括了参数处理器(ParameterHandler)的使用,它将Java对象转换为SQL可识别的...

    深入浅出MyBatis技术原理与实践

    《深入浅出MyBatis技术原理与实践》是一本专注于解析MyBatis这一流行Java持久层框架的专业书籍。MyBatis作为一个轻量级的ORM(对象关系映射)框架,它将SQL语句与Java代码分离,使得开发更加灵活且易于维护。这本书...

    深入浅出Mybatis原理与实战

    通过动态SQL,MyBatis可以生成根据条件变化的复杂SQL语句。 MyBatis的注解方式简化了XML配置,开发者可以直接在Mapper接口的方法上使用注解来定义SQL。这种方式更加简洁,但灵活性相对较低。 在MyBatis的执行流程...

    从深入到浅出MyBatis技术的原理与实战操作. PDF

    MyBatis是一个优秀的Java持久层框架,它支持定制化SQL、存储过程以及高级映射。MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。MyBatis可以使用简单的XML或注解进行配置和原始映射,将接口和Java的...

    Mybatis资料

    总的来说,《深入浅出MyBatis技术原理与实战》这本书会详细讲解Mybatis的各个知识点,包括基本概念、配置、映射文件的编写、动态SQL、缓存机制、与Spring的整合以及Mybatis Plus的使用等。通过学习这本书,开发者...

    3-2_MyBatis持久层框架.pdf

    总之,《3-2_MyBatis持久层框架.pdf》深入浅出地介绍了MyBatis的使用方法,从基础操作到高级特性,为读者提供了全面的学习资源。通过学习,开发者能够熟练掌握MyBatis框架,提升数据库操作的效率和代码质量。

Global site tag (gtag.js) - Google Analytics