题记:本文对 Mybatis 框架相关内容进行整理,从最开始使用JDBC 操作数据库,理解 DAO 层底层需要执行的步骤,到仿照 MyBatis 自定义框架,对 MyBatis 框架结构进行梳理。之后再介绍 MyBatis 框架的基本使用以及常用特性,了解 MyBatis 的日常应用,最后深入框架源码去感受 MyBatis 框架的精妙设计。
public static void main(String[] args) { Connection connection = null; PreparedStatement preparedStatement = null; ResultSet resultSet = null; try { // 1. 加载数据库驱动 Class.forName("com.mysql.jdbc.Driver"); // 2. 通过驱动管理类获取数据库连接 connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "root"); String sql = "select * from user where username = ?"; // 3. 获取预编译语句 preparedStatement = connection.prepareStatement(sql); // 4. 设置预编译语句参数 preparedStatement.setString(1, "tom"); // 5. 执行SQL, 处理结果集 resultSet = preparedStatement.executeQuery(); while (resultSet.next()) { int id = resultSet.getInt("id"); String username = resultSet.getString("username"); user.setId(id); user.setUsername(username); } System.out.println(user); } catch (Exception e) { e.printStackTrace(); } finally { // 6. 释放资源 if (resultSet != null) { try { resultSet.close(); } catch (SQLException e) { e.printStackTrace(); } } if (preparedStatement != null) { try { preparedStatement.close(); } catch (SQLException e) { e.printStackTrace(); } } if (connection != null) { try { connection.close(); } catch (SQLException e) { e.printStackTrace(); } } } } 复制代码
使用端:
提供核心配置文件
/** * sqlMapConfig.xml:存放数据源信息,引入mapper.xml * Mapper.xml:SQL语句的配置文件信息 */ 复制代码
框架端:
读取配置文件
/** * 读取完以后以流的形式存在,可创建JavaBean来存储 */ public class Configuration { // 数据源 private DataSource dataSource; // map集合: key:statementId value:MappedStatement private Map<String,MappedStatement> mappedStatementMap = new HashMap<String, MappedStatement>(); } public class MappedStatement { //id private Integer id; //sql语句 private String sql; //输入参数 private Class<?> paramterType; //输出参数 private Class<?> resultType; } 复制代码
解析配置文件
创建SqlSessionFactoryBuilder类
使用 dom4j 解析配置文件,将解析出来的内容封装到 Configuration 和 MappedStatement 中
创建SqlSessionFactory
创建SqlSessionFactory的实现类DefaultSqlSession,并实现openSession() 方法,获取sqlSession接口的实现类实例对象 ( 传递Configuration 对象)
创建SqlSession接口及实现类 主要封装CRUD方法
方法:selectList(String StatementId,Object param)查询所有
selectOne(String StatementId,Object param)查询单个
close() 释放资源
具体实现:封装JDBC完成对数据库表的查询操作
Executor 类,从Configuration类中获取,DataSource、SQL、paramterType、resultType,通过反射设置预编译语句占位符的值,执行SQL,通过内省,通过列名与对象属性的对应关系解析结果为对象
使用代理模式来创建接口的代理对象
<!--mybatis--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.5</version> </dependency> <!--mysql驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> <scope>runtime</scope> </dependency> 复制代码
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="userMapper"> <select id="findAll" resultType="com.lagou.domain.User"> select * from User </select> </mapper> 复制代码
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql:///test"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <mappers> <mapper resource="com/lagou/mapper/UserMapper.xml"/> </mappers> </configuration> 复制代码
注:增删改需要提交事务或设置自动提交
示例:
InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); // SqlSession sqlSession = sqlSessionFactory.openSession(true); // 带参数true 为自动提交事务 SqlSession sqlSession = sqlSessionFactory.openSession(); // namespace.id int insert = sqlSession.insert("userMapper.add", user); System.out.println(insert); //提交事务 sqlSession.commit(); sqlSession.close(); 复制代码
SQL语句的主体结构,在编译时尚无法确定,只有等到程序运行起来,在执行的过程中才能确定,这种SQL叫做动态SQL。
常用标签:
<where>
<if>
<select id="findByCondition" parameterType="user" resultType="user"> select * from User <where> <if test="id!=0"> and id=#{id} </if> <if test="username!=null"> and username=#{username} </if> </where> </select> 复制代码
<foreach>
属性:
<select id="findByIds" parameterType="list" resultType="user"> <include refid="selectUser"></include> <where> <foreach collection="array" open="id in(" close=")" item="id" separator=","> #{id} </foreach> </where> </select> <!-- List ids = new ArrayList(); ids.add(1); ids.add(2); Map params = new HashMap(); params.put("ids", ids); params.put("title", "中国"); --> <select id="dynamicForeach3Test" resultType="Blog"> select * from t_blog where title like "%"#{title}"%" and id in <foreach collection="ids" index="index" item="item" open="(" separator="," close=")"> #{item} </foreach> </select> 复制代码
<sql>
<include>
@Insert:新增
@Update:更新
@Delete:删除
@Select:查询
@Result:实现结果集封装 [<id>, <result>, <association>, <collection>]
@Results:可以与@Result 一起使用,封装多个结果集<resultMap>
@One:实现一对一结果封装
@Many:实现一对多结果集封装
public interface UserMapper { @Select("select * from user") @Results({ @Result(id = true,property = "id",column = "id"), @Result(property = "username",column = "username"), @Result(property = "password",column = "password"), @Result(property = "birthday",column = "birthday"), @Result(property = "roleList",column = "id",javaType = List.class, many = @Many(select ="com.lagou.mapper.RoleMapper.findByUid")) }) List<User> findAllUserAndRole(); } public interface RoleMapper { @Select("select * from role r,user_role ur where r.id=ur.role_id and ur.user_id=#{uid}") List<Role> findByUid(int uid); } 复制代码
Mybatis 缓存
一级缓存是 SqlSession 级别的缓存,由sqlSession 对象中的 HashMap 数据结构来储存, 不同sqlSession 之间的缓存互不影响,一级缓存默认开启
二级缓存是 mapper 级别的缓存,二级缓存需手动开启
Map 中 CacheKey
CacheKey cacheKey = new CacheKey(); // MappedStatement的id // id: namespace + SQLid cacheKey.update(ms.getId()); // offset 0 cacheKey.update(rowBounds.getOffset()); // limit就是 Integer.MAXVALUE cacheKey.update(rowBounds.getLimit()); // 具体的SQL语句 cacheKey.update(boundSql.getSql()); // SQL中带的参数 cacheKey.update(value); ... if (configuration.getEnvironment() != null) { // environmentId cacheKey.update(configuration.getEnvironment().getId()); } 复制代码
二级缓存与一级缓存流程类似,但二级缓存基于 mapper 文件的 namespace
二级缓存底层还是 HashMap 结构
执行 commit() 操作会清空二级缓存数据
在全局配置文件sqlMapConfig.xml 中加入
<!-- 开启二级缓存 --> <settings> <setting name="cacheEnabled" value="true"/> </settings> 复制代码
在 Mapper.xml 中开启缓存
<!-- 开启二级缓存 --> <!-- 空标签,默认 type=PerpetualCache的相对路径 也可以通过实现 Cache 接口来自定义缓存 --> <cache></cache> 复制代码
pojo类实现序列化接口
因为二级缓存存储介质并非内存一种,可能会序列化到硬盘中
<dependency> <groupId>org.mybatis.caches</groupId> <artifactId>mybatis-redis</artifactId> <version>1.0.0-beta2</version> </dependency> 复制代码
Mapper.xml
<cache type="org.mybatis.caches.redis.RedisCache" /> 复制代码
redis.host=localhost redis.port=6379 redis.connectionTimeout=5000 redis.password= redis.database=0 复制代码
mybatis-redis 在存储数据的时候,使用的 hash 结构 key: namespace field: Cachekey value: result
因为需要序列化与反序列化,所以第二次从缓存中获取的对象和之前的对象并不是同一个
Mybatis作为一个应用广泛的优秀的ORM框架,这个框架具有强大的灵活性,在四大组件(Executor、StatementHandler、ParameterHandler、ResultSetHandler)处提供了简单易用的插件扩展机制。Mybatis对持久层的操作就是借助于四大核心对象。MyBatis支持用插件对四大核心对象进行拦截,对mybatis来说插件就是拦截器,用来增强核心对象的功能,增强功能 本质上是借助于底层的动态代理实现的 ,换句话说, MyBatis中的四大对象都是代理对象 。