MyBatis的设计思路
ORM(Object Relation Mapping)模型将数据库存储数据与POJO对象进行映射,从而简化数据转化的复杂度;如果说Hibernate是完全封装的ORM框架,则myBatis则是半完全封装的ORM框架,因为myBatis需要手动映射SQL返回结果与POJO对象的映射;
java连接mysql一般有四种方式,不基于连接池的jdbc和基于连接池的dbcp, c3p0, jndi:
#1 jdbc:不使用连接池,每次进行DB操作都通过java.sql.DriverManager.getConnection获取一个新的connection,并通过openSession()获取一个交互session;
#2 dbcp:基于org.apache.commons.dbcp.BasicDataSource并创建connection pool,BasicDataSourceFactory.createDataSource获取DBCP数据源,然后获取可重复使用的connection;
#3 c3p0:基于DataSource并创建connection pool,ComboPooledDataSource.getConnection获取可重复使用的connection。
#4 jndi:基于DataSource并创建connection pool,InitialContext.getConnection获取connection。
myBatis解决了原生JDBC访问DB的几大问题:
#1 DB Connection频繁的开启和关闭会造成资源的浪费,所以使用DB Connection Pool通过重复利用Connection可以极大降低DB资源的消耗;connection创建session并开启transaction,session结束后transaction也关闭,并将connection归还给pool;
#2 使用可配置的方式兼容多种DataSource(jndi, c3p0, dbcp)的连接池;
#3 越来越多的业务需要根据数据来动态生成sql语句,传统的preparedStatement都是基于固定的sql来填充参数,mybatis提供if-else等标签根据业务数据动态生成并组装sql;
#4 mybatis提供ResultMap等自动映射的方式对查询结果进行解析,并提供[sql + params]作为key的方式将查询结果进行缓存;
#5 mybatis提供<sql />标签将一些共用的sql片段抽取出来,供多个地方引用;
对比MyBatis和Hibernate
#1 myBatis中的SQL是在开发的时候就已经写好的,并且可以细粒度的优化SQL语句,hibernate则是完全根据Object生成SQL,性能上会有一些影响;但同时hibernate的DB关联性低,可移植性高,myBatis由于定制化了大量的SQL,则在迁移DB的时候需要考虑兼容性;
Mybatis可接编写原生态sql,可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发;但缺点是mybatis无法做到数据库无关性,如果需要实现支持多种数据库则需要自定义多套sql映射文件,myBatis同样支持通过java bean定义逆向生成CRUD操作对应的SQL。
#2 Hibernate提供的是一种全表映射的模型,因此在做关联查询的时候hibernate会将相关表的所有字段都加载到内存,然后再将数据封装成需要的对象;而myBatis会根据resultMap仅仅将需要的数据加载到内存,并自动转化成需要的对象,可以极大提升效率。
#3 Hibernate有一整套DAO对象的状态管理机制,myBatis则没有完整的DAO管理机制,需要手动维护DAO对象的数据更新操作;
#4 Hibernate的ORM映射关系是固定的,如果某些字段或者某些查询条件需要动态生成则只能通过HQL实现(性能较差),而myBatis天然支持SQL的动态生成,可以支持table名字或者column名字动态变化的场景;
#5 Hibernate不能有效支持存储过程,myBatis天然支持存储过程,store procedure的优势在于将业务逻辑封装到DB端,更安全稳定、执行性能也高,但劣势在于bug tracking和可移植性;
在spring中集成myBatis
首先在spring配置文件service.xml中配置 DBCP数据源,TxManager 和SqlSession的封装。
1 <beans xmlns="http://www.springframework.org/schema/beans"> 2 <bean id="dbcpDataSource" 3 class="org.apache.commons.dbcp.BasicDataSource"> 4 <property name="driverClassName"> 5 <value>com.mysql.jdbc.Driver</value> 6 </property> 7 <property name="url"> 8 <value>jdbc:mysql://localhost:3306/ychenDemo</value> 9 </property> 10 <property name="username"> 11 <value>root</value> 12 </property> 13 <property name="password"> 14 <value>password</value> 15 </property> 16 </bean> 17 18 <bean id="mybatisTxManager" 19 class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 20 <property name="dataSource" ref="dbcpDataSource" /> 21 </bean> 22 23 <bean id="sqlSessionFactory" 24 class="org.mybatis.spring.SqlSessionFactoryBean"> 25 <property name="dataSource" ref="dbcpDataSource" /> 26 <property name="configLocation" 27 value="mybatis/mybatis-config.xml"></property> 28 </bean> 29 30 <bean id="myBatisBaseMapper" 31 class="org.mybatis.spring.mapper.MapperFactoryBean" abstract="true" 32 lazy-init="true"> 33 <property name="sqlSessionFactory" ref="sqlSessionFactory" /> 34 </bean> 35 36 <bean id="userMapper" parent="myBatisBaseMapper"> 37 <property name="mapperInterface" 38 value="ychen.demo_mybatis.mapper.UserMapper" /> 39 </bean> 40 41 <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"> 42 <constructor-arg index="0" ref="sqlSessionFactory" /> 43 </bean> 44 </beans>
mybatis提供两种DB操作的接口:
-1 通过sqlSessionTemplate提供统一接口,并在参数中指定statementID和parameterList的方式;
1 public List<Person> getPersonById(Long personId) { 2 Map<String, Long> params = Maps.newHashMap(); 3 params.put("personId", personId); 4 List<Person> result = template.selectList("getPersonById", params); 5 return result; 6 } 7 8 >>>>>>>>>>>>> 9 <mapper namespace="personMapper"> 10 <select id="getPersonById" resultType="com.ychen.Person"> 11 SELECT * FROM person WHERE id = #{personId} 12 </select> 13 </mapper>
-2 通过定义mapper interface,并使用SqlSessionFactoryBean和MapperFactoryBean动态代理的方式生成mapper实现类,mapper interface中可以自定义多个DB访问接口,内部实现依旧是转换成sqlSessionTemplate统一接口的方式,只是封装性更好。
1 @Component 2 public interface UserMapper { 3 void insert(UserEntity user); 4 UserEntity getOne(Long id); 5 } 6 7 >>>>>>>>> 8 <mapper namespace="ychen.demo_mybatis.mapper.UserMapper"> 9 <select id="getOne" parameterType="Long" resultMap="BaseResultMap"> 10 SELECT <include refid="Base_Column_List" /> 11 FROM payment_session 12 WHERE id = #{id} 13 </select> 14 </mapper> 15 16 >>>>>>>>> 17 UserMapper userMapper = 18 applicationContext.getBean("userMapper", UserMapper.class); 19 UserEntity userEntity = userMapper.getOne(1L);
构建sqlSessionFactory所需的myBatis-config.xml配置
1 <configuration> 2 <!-- resource属性引入外部定义的key/value变量,也可通过property标签定义内部变量 --> 3 <properties resource="org/mybatis/example/mybatis-config.properties"> 4 <property name="username" value="dev_user"/> 5 <property name="password" value="F2Fa3!33TYyg"/> 6 </properties> 7 <!-- 定义mybatis的运行变量 --> 8 <settings> 9 <setting name="cacheEnabled" value="true"/> 10 <setting name="lazyLoadingEnabled" value="true"/> 11 <setting name="multipleResultSetsEnabled" value="true"/> 12 <setting name="useColumnLabel" value="true"/> 13 <setting name="useGeneratedKeys" value="false"/> 14 <setting name="autoMappingBehavior" value="PARTIAL"/> 15 <!-- SIMPLE|REUSE|BATCH --> 16 <setting name="defaultExecutorType" value="SIMPLE"/> 17 <setting name="defaultStatementTimeout" value="25"/> 18 <setting name="defaultFetchSize" value="100"/> 19 <setting name="safeRowBoundsEnabled" value="false"/> 20 <setting name="mapUnderscoreToCamelCase" value="false"/> 21 <setting name="localCacheScope" value="SESSION"/> 22 <setting name="jdbcTypeForNull" value="OTHER"/> 23 <setting name="lazyLoadTriggerMethods" 24 value="equals,clone,hashCode,toString"/> 25 </settings> 26 <!-- 定义常用类型的简写别名,同时mybatis为java常用类型提供了缺省别名 --> 27 <typeAliases> 28 <typeAlias type="leo.chen.domain.UserAccount" alias="UserAccount" /> 29 <typeAlias type="leo.chen.domain.SpecialCar" alias="SpecialCar" /> 30 </typeAliases> 31 <!-- 处理参数和返回结果在java-type和db-type间的转换,同时myBatis提供常用类型的handler --> 32 <!-- 通过继承BaseTypeHandler类或者实现TypeHandler接口,可以定制化转换方法 --> 33 <typeHandlers> 34 <typeHandler handler="com.active.services.endurance.mybatis.UUIDTypeHandler" 35 javaType="java.util.UUID" jdbcType="VARCHAR"/> 36 </typeHandlers> 37 <!-- myBatis创建结果对象实例时使用默认的工厂类实现,通过objectFactory可以替换默认的工厂 --> 38 <objectFactory type="leo.chen.manager.CustomizedObjectFactory"> 39 <property name="property1" value="value1"> 40 </objectFactory> 41 <!-- 用于对mybatis处理流程中某些方法的扩展处理,如缓存一致性,如分页 --> 42 <plugins> 43 <plugin interceptor="org.mybatis.example.validationPlugin"> 44 <property name="isThrowExp" value="true"/> 45 </plugin> 46 </plugins> 47 <!-- 定义mybatis的执行环境,包括事务管理、数据源等配置 --> 48 <environments default="development"> 49 <environment id="development"> 50 <transactionManager type="JDBC"> </transactionManager> 51 <dataSource type="POOLED"> 52 <property name="driver" value="${driver}"/> 53 <property name="url" value="${url}"/> 54 <property name="username" value="${username}"/> 55 <property name="password" value="${password}"/> 56 <property name="defaultTransactionlsoltionLevel" value="${txLevel}"/> 57 <property name="poolMaximumActiveConnection" value="10"/> 58 <property name="poolMaximumldleConnection" value="10"/> 59 <property name="poolMaximumCheckoutTime" value="20000"/> 60 <property name="poolTimeToWait" value="20000"/> 61 <property name="poolPingQuery" value="NOPINGQUERYSET"/> 62 <property name="poolPingEnabled" value="false"/> 63 <property name="poolPingConnectionsNotUsedFor" value="0"/> 64 </dataSource> 65 </environment> 66 </environments> 67 <!-- 数据库厂商标识 --> 68 <databaseIdProvider type=""></databaseIdProvider> 69 <!-- 定义mapper映射文件 --> 70 <mappers> 71 <mapper resource="mybatis/productMapper.xml" /> 72 </mappers> 73 </configuration>
#1 mybatis提供两种事务管理配置,JDBC和MANAGED,前者使用正常的JDBC的事务管理,也就是对java.sql.Connection事务管理的封装,依赖于从data source获取的连接来管理实务;后者将事务管理交由WEB容器进行管理,也就是说调用ManagedTransaction的commit和rollback并不会有任何动作;如果myBatis跟spring集成,则不需要配置此项。
#2 mybatis提供三种数据源的的配置,POOLED, UNPOOLED和JNDI,POOLED是在UNPOOLED的封装上添加了connection缓存管理的实现,UNPOOLED的实现仅仅是打开和关闭连接;而JNDI则是通过jndi服务查找代理的data source实现;如果myBatis跟spring集成,则也不需要配置此项。
#3 localCacheScope表示设置一级缓存的使用范围,缺省为SESSION,如果设置为STATEMENT则表示一级缓存仅限于一次statement使用,也就是禁用缓存。鉴于mybatis一级缓存实现不完整,容易造成数据一致性问题,因此建议使用STATEMENT。
#4 <plugins>标签允许在mybatis内部的四个大的处理流程中插入自定义的处理方法:
-1 Executor:处理mybatis的整个流程和二级缓存的使用
-2 ParameterHandler:处理mybatis对sql传入参数的赋值,
-3 StatementHandler:处理mybatis与db进行交互的规则和一级缓存的修改,
-4 ResultSetHandler:处理mybatis对结果集的映射规则;
具体的实现类需要实现Interceptor接口及对应的方法。
1 public interface Interceptor { 2 //通过invocation.getTarget()可以定制不同阶段的plugin 3 Object intercept(Invocation invocation) throws Throwable; 4 Object plugin(Object target); 5 void setProperties(Properties properties); 6 }