引言
1. 安装
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
2. XML 配置
<?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">
2.1 Configuration 配置
2.1.1 properties(属性)
- 使用一:maven 工程 resource 路径下(若路径下存在此配置文件,则无需填充 property 属性)
<properties resource="jdbcConfig.properties"> <property name="driver" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/cyt_mybatis"/> <property name="username" value="root"/> <property name="password" value="1234"/> </properties>
- 使用二:url 路径【协议 主机 端口 URI】(以下为 maven 文件对应的 url 路径)
<properties url="file:///E:/JavaLearing/code/testMybatis/src/main/resources/jdbcConfig.properties"> </properties>
2.1.2 settings(设置)
- 使用一:设置打印 SQL 语句,方便查错
- 使用二:支持二级缓存
- 使用三:支持延迟加载
<setting name="lazyLoadingEnabled" value="true"/> <setting name="aggressiveLazyLoading" value="true"/>
2.1.3 typeAliases(类型别名)
- **使用一:**指定配置别名的实体类,指定别名后不区分大小写
- <typeAliases> <typeAlias type="com.cyt.domain.User" alias="user"</typeAlias> </typeAliases>
- **使用二**:指定配置别名的包,该包下的实体类均会注册别名,类名就是别名,指定别名后不区分大小写
- <typeAliases> <package name="com.cyt.domain"/> </typeAliases>
2.1.4 environments(环境配置)
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC">
<property name="closeConnection" value="false"/>【选填,用于阻止默认关闭行为】
</transactionManager>
<dataSource type="POOLED">
<!-- 其中 ${xxx} 必须与配置文件 jdbcConfig.properties 中的 key 保持一致 -->
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
- 补充:
- 尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境,即一种类型的数据库
- Spring + MyBatis 无需配置事务管理器, Spring 模块会使用自带的管理器来覆盖前面的配置
- dataSource
- 为了使用延迟加载,dataSource 是必须配置的
- 三种内建的数据源类型 type=”[ UNPOOLED | POOLED | JNDI ]
- UNPOOLED | POOLED 涉及连接池的概念
- JNDI 用于 EJB 或应用服务器这类容器使用,可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用
- 报错记录: property name 的顺序和内容必须和 jdbcConfig.properties 中一致,否则会报错 BuilderException:Error parsing SQL Mapper Configuration.. unknown DataSource property
2.1.5 mappers(映射器)
<mappers>
1. 单个 mapper 映射
1.1。 类路径的资源引用: <mapper resource="com/cyt/dao/IUserDao.xml"/>
1.2. 映射器接口实现类的完全限定类名: <mapper class="com.cyt.dao.IUserDao"/>
1.3. 使用完全限定资源定位符(URL):
<mapper url="file:///E:/JavaLearing/code/testMybatis/src/main/resources/.com/cyt/dao/IUserDao.xml"/>
2. 多个 mapper,将包内的映射器接口实现全部注册为映射器
<package name="org.mybatis.builder"/>
</mappers>
2.1.6 其他
typeHandlers:将 预处理语句设置参数或从结果集取值 以合适类型转换成 java 类型;
or
特殊_枚举处理器(处理任何继承 Enum 的类):
- 补充:支持重写,需实现 org.apache.ibatis.type.TypeHandler 接口 or 继承 org.apache.ibatis.type.BaseTypeHandler 类
- objectFactory(对象工厂)
- plugins(插件):实现 Interceptor 接口,并指定想要拦截的方法签名
- databaseIdProvider(数据库厂商标识)
3. XML 映射
<?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="com.cyt.dao.IUserDao"> <resultMap/> <select/> <sql/> 等 </mapper>
3.1 基础
3.1.1 sql 标签
-
3.1.2 resultMap
3.1.3 CURD 语句
select 模糊查询:
<select id="findByName" parameterType="string" resultType="com.cyt.domain.User"> select * from user where username like '%${value}%' </select>
select 包含实体类的条件类查询:
<!-- 根据 queryVo 的条件查询用户 --> <select id="findUserByVo" parameterType="com.cyt.domain.QueryVo" resultType="com.cyt.domain.User"> select * from user where username like #{user.username} </select>
select 返回数字:
<select id="findTotal" resultType="java.lang.Integer"> select count(*) from user; </select>
insert:
<insert id="saveUser" parameterType="com.cyt.domain.User"> <!-- 配置插入操作后,获取插入数据的 id ,其中keyProperty对应的是实体类中的名称--> <selectKey keyProperty="id" keyColumn="id" resultType="int" order="AFTER"> select last_insert_id(); </selectKey> <!-- 字段的顺序必须和 User 类中的类成员变量顺序一致,且 #()取值与 getXXX 一致--> insert into user(username,address,sex,birthday)values(#{username},#{address},#{sex},#{birthday}); </insert> //自增主键 <insert id="saveUser" parameterType="user" useGeneratedKeys="true" keyProperty="id">
delete
<delete id="deleteUser" parameterType="java.lang.Integer"> delete from user where id=#{user_id}; </delete>
update
<update id="updateUser" parameterType="com.cyt.domain.User"> update user set username=#{username},address=#{address},sex=#{sex},birthday=#{birthday} where id=#{id}; </update>
3.2 动态 SQL
3.2.1 foreach & if
<!--根据queryvo中提供的id集合实现查询用户列表-->
<select id="findUserInIds" parameterType="QueryVo" resultType="user">
<include refid="defaultUser"></include> 【对 <sql/> 的引用】
<where>
<if test="ids != null or ids.size() > 0">
<foreach collection="ids" open="id in (" close=")" item="uid" separator=",">${uid} </foreach>
</if>
</where>
</select>
3.2.2 set & trim
<update id="testSetLabel" parameterType="user">
update user
<trim prefix="set" suffix="where id=#{id}" suffixOverrides=",">
<if test="address != null and address != ''">address = #{address}, </if>
<if test="username != null and username != ''"> username = #{username}, </if>
</trim>
</update>
3.2.3 choose & when(等同于 switch case)
<select id="testChooseLabel" parameterType="user" resultType="User">
<include refid="defaultUser"></include>
<where>
<choose>
<when test="username != null and username != ''"> and username = #{username} </when>
<when test="sex != null and sex != ''">and sex = #{sex} </when>
<otherwise> and address = #{address} </otherwise>
</choose>
</where>
</select>
3.2.4 map
使用一:
<select id="testMapLabel" parameterType="java.util.Map"> insert into user <foreach collection="userMaps.keys" item="key" open="(" close=")" separator=",">${key}</foreach> values <foreach collection="userMaps.values" item="value" open="(" close=")" separator=",">#{value}</foreach> </select>
使用二:
<select id="testMapLabel" parameterType="java.util.Map"> insert into user <foreach collection="userMaps" item="key" index="key1" open="(" close=")" separator=",">${key1</foreach> values <foreach collection="userMaps" item="value" index="value1" open="(" close=")" separator=",">#{value}</foreach> </select>
补充: foreach 标签的 collection 属性默认值为:_parameter,非默认情况下,应与 dao 接口的方法保持一致,eg. void testMapLabel(@**Param(value = “userMaps”) **Map
userMap)- 3.2.5 mybatis 动态 sql 支持
注意:mybatis 3.2.x 版本之后新增** **LanguageDriver 接口
使用配置:
sqlMapConfig.xml : mymyLanguage 是 LanguageDriver 的实现类
方式一: <typeAliases> <typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/> </typeAliases> 方式二: <settings> <setting name="defaultScriptingLanguage" value="myLanguage"/> </settings>
IUserDao.xml
<select id="findAll" lang="myLanguage"> select * from user; </select>
3.3 多表查询
resultMap
- association 的两种方法【第二种方法更为简洁,推荐使用】
- 要求:实体类中必须有和关联实体类相关的属性;
- property 对应实体类属性名,column 对应最终查询出的表结构的命名;
; - 必须指定 javaType: 表示关联类的全限定类名,此处表示方式是因为 sqlMapConfig 有别名设置;
实现一: ```java
对应的 sql 语句:
- association 的两种方法【第二种方法更为简洁,推荐使用】
- 实现二:
```java
<resultMap id="accountUserMap" type="account">
<id property="id" column="id"></id>
<result property="uid" column="uid"></result>
<result property="money" column="money"></result>
<!--延迟缓存-->
<association property="user" column="uid" javaType="user" select="com.cyt.dao.IUserDao.findUserById">
<!-- 注意要在 com/cyt/dao/IUserDao.xml 中有 findUserById 的 sql 语句 -->
</association>
</resultMap>
对应的 sql 语句:
1.IAccountDao.xml
<select id="findAll" resultMap="accountUserMap">
select * from account;
</select>
2.IUserDao.xml
<select id="findUserById" parameterType="java.lang.Integer" resultType="com.cyt.domain.User" useCache="true">
select * from user where id=#{id}
</select>
- collection 同有两种方法,此处略
注意: ofType,不写或写成其他的会报错:空指针异常
<resultMap id="roleMap2" type="role">
<!--column 值必须与sql语句生成的表的字段名一致-->
<id property="id" column="rid"></id>
<result property="roleName" column="rname"></result>
<result property="roleDesc" column="rdesc"></result>
<collection property="users" ofType="user">
<id property="id" column="id"></id>
<result property="username" column="username"></result>
<result property="address" column="address"></result>
<result property="sex" column="sex"></result>
<result property="birthday" column="birthday"></result>
</collection>
</resultMap>
对应的 sql 语句
<!-- 多对多映射 获取角色所对应的用户 -->
<select id="findRoleUsers" resultMap="roleMap2">
select u.*,r.id as rid,r.role_name as rname,r.role_desc as rdesc from role r
left outer join user_role ur on ur.rid=r.id
left outer join user u on u.id=ur.uid;
</select>
4. Annotation 映射
4.1 动态 SQL 注解
@select、@update、@insert、@delete (以下 test 支持动态 sql)
- @Select(“SELECT * FROM users WHERE id IN (#{userIdList})”)
@Lang(SimpleSelectInLangDriver.class)
List
@Update(“UPDATE users (#{user}) WHERE id = #{id}”)
@Lang(SimpleUpdateLangDriver.class)
void updateUsersById(User user);@Insert(“INSERT INTO users (#{user})”)
@Lang(SimpleInsertLangDriver.class)
void insertUserDAO(User user);@Delete(“DELETE FROM user WHERE id=#{user_id}”)
@Lang(SimpleDeleteLangDriver.class)
void deleteUsersById(User user);
- 举例 : SimpleSelectInLangDriver 实现类
public class SimpleSelectInLangDriver extends XMLLanguageDriver implements LanguageDriver{ }
4.2 建立实体类与数据库的映射
注解实现 resultMap
@Select("select * from user") @Results(id="userMap", value = { @Result(id=true, column = "id", property = "userId"), @Result(id=true, column = "username", property = "userName"), @Result(id=true, column = "address", property = "userAddress"), @Result(id=true, column = "sex", property = "userSex"), @Result(id=true, column = "birthday", property = "userBirthday") }) List<User1> findAll(); @Select("select * from user where username like '%${value}%'") @ResultMap(value = "userMap") List<User1> findByName(String name);
4.3 多表查询
一对一
1. IAccountDao.class @Results(id="accountMap", value = { @Result(id=true,property = "id",column = "id"), @Result(property = "uid",column = "uid"), @Result(property = "money",column = "money"), @Result(property = "user",column = "uid",one = @One(select = "com.cyt.dao.IUserDao_annotation.findUserById",fetchType = FetchType.EAGER)) //one 表示一对一的映射关系,fetchType.eager 是及时加载 }) @Select("select * from account") List<Account> findAll(); 2. IUserDao.class @Select("select * from user where id=#{id}") @ResultMap("userMap") User findUserById(Integer id);
一对多
1. IUserDao.class @Select("select * from user") @Results(value = { @Result(id=true, column = "id", property = "id"), @Result(column = "username", property = "username"), @Result(column = "address", property = "address"), @Result(column = "sex", property = "sex"), @Result(column = "birthday", property = "birthday"), @Result(property = "accounts", column = "id", many = @Many(select = "com.cyt.dao.IAccountDao_annotation.findAccountById",fetchType = FetchType.LAZY)) }) List<User2> findAllUserAccount(); 2. IAccountDao.class @Select("select * from account where uid=#{uid}") @Results(value = { @Result(id=true,property = "id",column = "id"), @Result(property = "uid",column = "uid"), @Result(property = "money",column = "money")}) List<Account> findAccountById(Integer uid);
5. 执行 & 过程分析
5.1 执行过程
(1)读取配置文件
in = Resources.getResourceAsStream(“SqlMapConfig_CURD.xml”);
(2)创建 SqlSessionFactory 工厂 【创建者模式:细节隐藏,直接调用】
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
factory = builder.build(in);
(3)使用工厂创建 sqlSession 对象【工厂模式:解耦】
sqlSession = factory.openSession(true); // 设置自动提交,否则需手动补充 sqlSession.commit()
(4)获取 Dao 类【代理模式:不修改源码,对已有方法 selectList 增强】
userDao = sqlSession.getMapper(IUserDao.class); // 代理 Dao
List
(5)执行 Dao 中的方法
List
(6)释放资源
sqlSession.close(); in.close();
5.2 自定义Mybatis(理解源码)
- 第一步:
- Resource 读取指定路径下的 xml 文件,获取 InputStream 对象,即 config;
- 第二步:
- SqlSessionFactoryBuilder 调用 XMLConfigBuilder.loadConfiguration(config) 解析 xml 文件,获取 jdbcConfig 和 mapper(调用 loadMapperConfiguration 或 loadMapperAnnotation ),并包装成一个对象 cfg;
- 第三步:
- DefaultSqlSessionFactory 传递 cfg,并调用 DefaultSqlSession() 方法;
第四步:
- DefaultSqlSession 构造函数中调用 dataUtil ,利用 cfg 配置信息注册数据驱动,获取数据连接 conn;
public static Connection getConnection(Configuration cfg){ Class.forName(cfg.getDriver()); return DriverManager.getConnection(cfg.getUrl(),cfg.getUsername(),cfg.getPassword()); }
- DefaultSqlSession 构造函数中调用 dataUtil ,利用 cfg 配置信息注册数据驱动,获取数据连接 conn;
第五步:DefaultSqlSession getMapper 方法实现代理 dao;
userDao = sqlSession.getMapper(IUserDao.class); ———————————————————————————————————————————————————————————————— 以下为自定义代码,理解 mybatis 的执行过程 ———————————————————————————————————————————————————————————————— ——————————————————————————5.1 public <T> T getMapper(Class<T> daoInterfaceClass) { return (T)Proxy.newProxyInstance( daoInterfaceClass.getClassLoader(), new Class[]{daoInterfaceClass}, new MapperProxy(cfg.getMappers(), connection)); // 不指定返回类型的话,会报错 } ——————————————————————————5.2 class MapperProxy{ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //1. 获取方法名 String methodName = method.getName(); //2. 获取方法所在的类名称 String className = method.getDeclaringClass().getName(); //3. 组合 key String key = className + "." + methodName; //4. 获取 mappers 中的 Mapper 对象 Mapper mapper = mappers.get(key); //5. 判断是否有 mapper if (mapper == null) { throw new IllegalArgumentException("传入的参数有误"); } //6. 调用工具类执行查询所有 return new Executor().selectList(mapper, conn); } } ——————————————————————————5.3 public class Executor { public <E> List<E> selectList(Mapper mapper, Connection conn) { String queryString = mapper.getQueryString(); //select * from user String resultType = mapper.getResultType();// com.cyt.domain.User Class domainClass = Class.forName(resultType); 【解释之前 dao 类为什么实现 serialiable 接口】 //2.获取PreparedStatement对象 pstm = conn.prepareStatement(queryString); //3.执行SQL语句,获取结果集 rs = pstm.executeQuery(); //4.封装结果集 略 return list; } }
分析:映射文件是个大map,key 是 全限定类名,value 是 mapper,mapper 由 sql 语句 和 返回类型 两部分组成,以下是整个过程的图示;
5.3 补充
- builder、factory、session 的最佳作用域
- SqlSessionFactoryBuilder 方法作用域,一旦创建 SqlSessionFactory 便不再需要;
- SqlSessionFactory 应用作用域,如使用单例模式或者静态单例模式,鼓励重用;
- SqlSession 请求或方法作用域,与线程对应,非线程安全,不被共享,确保 sqlSession 用完即关闭,不能用于一个类的静态域,类的实例变量或将 SqlSession 实例引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession;
- 自定义注解(eg:注解 sql 查询):
- builder、factory、session 的最佳作用域
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {
_ _String value();
}
6. mybatis 缓存
6.1 一级缓存
- MyBatis 默认开启一级缓存,无需手动配置;
- 一级缓存是基于PerpetualCache 的 HashMap本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache就将清空;
- MyBatis 会在一次会话中,一个 SqlSession 对象中创建一个本地缓存(local cache),对于每一次查询,都会尝试根据查询的条件去本地缓存中查找是否在缓存中,如果在缓存中,就直接从缓存中取出,然后返回给用户;否则,从数据库读取数据,将查询结果存入缓存并返回给用户;
6.2 二级缓存
- resultMap 对应的实体类必须要序列化,不然会报 java.io.NotSerializableException;
- 与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),也是一个内存级别的缓存;
- 默认的二级缓存功能如下:
1.映射语句文件中所有的select语句将会被缓存;
2.映射语句文件中所有的insert update delete语句会刷新缓存;
3.缓存会使用(Least Flush Interval,LRU最近最少使用的)算法来收回;
4.根据时间表(如 no Flush Interval,没有刷新间隔),缓存不会以任何时间顺序来刷新;
5.缓存会存储集合或对象(无论查询方法返回什么类型的值)的1024个引用;
6.缓存会被视为read/wriete(可读/可写)的,意味着对象检索不是共享的,而且可以安全的被调用者修改,而不干扰其他调用者或者线程所做的潜在修改;
- mybatis 自带的二级缓存
- 存储于 sqlFactory 中,同一 factory 建立的多个 sqlSession 共享内存;
- xml 配置:
- mybatis 框架( SqlMapConfig.xml
); - 映射文件(IUserDao.xml mapper
); - 让当前的操作支持二级缓存
- mybatis 框架( SqlMapConfig.xml