Mybatis
1. 原理
- 配置基础配置XML,如SqlMapConfig.xml 
- 配置映射器XML,如mapper.xml 
- 解析XML,SqlSessionFactoryBuilder构建出SqlSessionFactory对象 
- SqlSessionFactory产生SqlSession对象 
- SqlSession下的四大对象 - Executor:执行器,由它统一调度其他三个对象来执行对应的SQL;
- StatementHandler:使用数据库的Statement执行操作;
- ParameterHandler:用于SQL对参数的处理;
- ResultHandler:进行最后数据集的封装返回处理;
 
// 配置文件
String resource = "SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 使用SqlSessionFactoryBuilder从xml配置文件中创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 创建数据库会话实例sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 查询单个/多个记录,根据用户id查询用户信息(namespace.select的id)
User user = sqlSession.selectOne("test.findUserById", 10);
List<User> list = sqlSession.selectList("test.findUserByName","小明");
// 插入user用户的信息
sqlSession.insert("test.insertUser", user);
// 删除用户
sqlSession.delete("test.deleteUserById",18);
// 更新用户
sqlSession.update("test.updateUser", user);
// 提交事务
sqlSession.commit();
1.1. 原始DAO开发
- 编写DAO接口类 
- 编写DAO实现类,实现类注入成员属性sqlSessionFactory,方法结构如下 
@Override
public User findUserById(Integer id) {
    SqlSession sqlSession = sqlSessionFactory.openSession();
    User user = sqlSession.selectOne("test.findUserById", id);
    // 释放资源
    sqlSession.close();
    return user;
}
缺点:由于SQLSession是线程不安全的,所以必须放在方法内,但同时就导致方法内有很多重复冗余的代码。
1.2. mapper代理开发
- 编写mapper.xml
- 编写mapper接口(类似于DAO接口)
- 通过SqlSession.getMapper(接口名.class)获取实现了该接口的代理类实例,即DAO实例。
- 两者需要遵循一些规范才能生效 - Mapper.xml文件中的namespace与mapper接口的类全限定名相同
- Mapper接口方法名和Mapper.xml中定义的每个statement的id相同
- Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同
- Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
 
2. 基础配置XML
2.1. 根元素configuration
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD SQL Map Config 3.0//EN"  
    "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
</configuration>
2.2. 配置Properties
<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>
加载顺序:
- 先加载<properties>内的<property>的属性
- 然后加载resource/url指定的文件内的属性,并覆盖已读同名属性
- 最后读取parameterType传进来的属性值,并覆盖已读同名属性
使用:配置了的属性可以用${属性名}来调用,包括mapper.xml里也能使用
2.3. 配置settings
<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
  <setting name="useGeneratedKeys" value="false"/>
</settings>
参考:settings全部属性
2.4. 配置连接池和事务管理,如果在Spring里配置则可以不写
<environments default="development">
    <environment id="development">
        <!-- 使用jdbc事务管理,事务由Mybatis控制 -->
        <transactionManager type="JDBC"></transactionManager>
        <!-- 配置数据库连接池 -->
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.jsbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </dataSource>
    </environment>
</environments>
2.5. 加载映射文件
<mappers>
    <!-- resource和url都能引用资源 -->
    <mapper resource="sqlmap/mapper.xml"/>
    <mapper url="file:///var/mappers/AuthorMapper.xml"/>
    <!-- 加载mapper接口,必须满足mapper代理模式规范,且xml文件名和接口类名相同,且在同一目录下-->
    <mapper class="org.mybatis.builder.PostMapper"/>
    <!-- 批量加载包内的mapper接口-->
    <package name="org.mybatis.builder"/>
</mappers>
2.6. 默认别名
| 别名 | 映射的类型 | 
|---|---|
| _byte | byte | 
| _long | long | 
| _short | short | 
| _int | int | 
| _integer | int | 
| _double | double | 
| _float | float | 
| _boolean | boolean | 
| string | String | 
| byte | Byte | 
| long | Long | 
| short | Short | 
| int | Integer | 
| integer | Integer | 
| double | Double | 
| float | Float | 
| boolean | Boolean | 
| date | Date | 
| decimal | BigDecimal | 
| bigdecimal | BigDecimal | 
| object | Object | 
| map | Map | 
| hashmap | HashMap | 
| list | List | 
| arraylist | ArrayList | 
| collection | Collection | 
| iterator | Iterator | 
2.7. 自定义别名
<typeAliases>
    <!-- 单个别名定义 -->
    <typeAlias alias="user" type="cn.itcast.mybatis.po.User"/>
    <!-- 批量别名定义,扫描整个包下的类,别名为类名(首字母大写或小写都可以) -->
    <package name="cn.itcast.mybatis.po"/>
    <package name="其它包"/>
</typeAliases>
3. 在Spring里配置Mybatis
<!-- 配置mybatis -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"></property>
    <!-- mapper扫描 -->
    <property name="mapperLocations" value="classpath:mybatis/*/*.xml"></property>
</bean>
<bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg ref="sqlSessionFactory" />
</bean>
4. XML映射文件
4.1. 根元素mapper
<?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:命名空间,随便写,一般保证命名空间唯一 -->
<mapper namespace="cn.itcast.mybatis.mapper.UserMapper">
</mapper>
4.2. statement配置
<!-- 添加用户 -->
<insert  id="insertUser" parameterType="cn.itcast.mybatis.po.User">
    insert into user(id,username,birthday,sex,address) values(#{id},#{username},#{birthday},#{sex},#{address})
</insert>
<!-- 删除用户 -->
<delete id="deleteUserById" parameterType="int">
    delete from user where id=#{id}
</delete>
<!-- 更新用户 -->
<update id="updateUser" parameterType="cn.itcast.mybatis.po.User">
    update user set username=#{username},birthday=#{birthday}, sex=#{sex},address=#{address} where id=#{id}
</update>
<!-- 查询用户,也可以用resultMap来映射返回值 -->
<select id="findUserById" parameterType="int" resultType="cn.itcast.mybatis.po.User">
    select * from user where id = #{id}
</select>
4.3. #{}和${}
#{}占位符
- 进行java类型和jdbc类型转换
- 传值时会带""
- 如果parameterType传入的是pojo类型,那么#{}中的变量名称必须是pojo中对应的属性.属性.属性...
- 如果parameterType传入的是基础类型值,#{}括号中可以是value或其它名称
- 可以有效防止sql注入
${}拼接符
- 不进行java类型和jdbc类型转换
- 传值时不带""
- 如果parameterType传入的是pojo类型,那么${}中的变量名称必须是pojo中对应的属性.属性.属性...
- 如果parameterType传输单个简单类型值,${}括号中只能是value
使用情景
- mybaties排序时使用order by动态参数时需要注意,使用${}而不用#{}
- 模糊查询:LIKE CONCAT('%',#{value},'%')或LIKE '%${value}%'
4.4. 定义resultMap
<resultMap id="detailedBlogResultMap" type="Blog">
    <constructor>
        <idArg column="blog_id" javaType="int"/>
    </constructor>
    <result property="title" column="blog_title"/>
    <association property="author" javaType="Author">
        <id property="id" column="author_id"/>
        <result property="username" column="author_username"/>
        <result property="password" column="author_password"/>
        <result property="email" column="author_email"/>
        <result property="bio" column="author_bio"/>
        <result property="favouriteSection" column="author_favourite_section"/>
    </association>
    <collection property="posts" ofType="Post">
        <id property="id" column="post_id"/>
        <result property="subject" column="post_subject"/>
        <association property="author" javaType="Author"/>
        <collection property="comments" ofType="Comment">
            <id property="id" column="comment_id"/>
        </collection>
        <collection property="tags" ofType="Tag" >
            <id property="id" column="tag_id"/>
        </collection>
        <discriminator javaType="int" column="draft">
            <case value="1" resultType="DraftPost"/>
        </discriminator>
    </collection>
</resultMap>
resultMap标签
- id:resultMap的标识,即statement内resultMap属性需要的值。
- type:映射的pojo的全限定类名,或类型别名。
- autoMapping:true(默认)或false。是否启动自动映射功能,true则自动查找与字段名小写同名的属性名,并调用setter方法。false则需要明确注明映射关系才会调用对应的setter方法。
- extends:继承指定标识的resultMap的映射
id子标签:用于设置主键字段和pojo属性的映射关系
result子标签:用于设置普通字段和pojo属性的映射关系
- property:pojo的属性名
- column:查询结果的字段名
constructor标签:使用指定参数列表的构造函数来实例化pojo。注意:其子元素顺序必须与参数列表顺序对应
- idArg子标签:标记该入参为主键
- arg子标签:标记该入参为普通字段(主键使用该子元素设置也是可以的)- column:查询结果的字段名
- JavaType:入参的java类型名
 
association 标签:pojo内部pojo类型属性的映射
- property:该pojo类型属性的属性名
- javaType:该属性的pojo类型名或别名
- resultMap:使用指定标识的resultMap映射
- column:传入关联查询的字段名,多个用,隔开
- select:关联查询的statement的id
collection标签:对应于一对多的集合映射
- property:该集合属性的属性名
- javaType:该集合属性的java属性类名或别名
- ofType:该集合内存的pojo类型名或别名
- resultMap:使用指定标识的resultMap映射
- column:传入关联查询的字段名,多个用,隔开
- select:关联查询的statement的id
延迟加载:select指定的查询只有在该属性的get方法被调用的时候才会执行,并把结果映射给该参数。需配置以下设置。
<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>
discriminator标签:鉴别器,根据某个字段的值来动态选择映射类型,如果都不符合则使用父标签定义的resultMap
- column:用来判断的字段的名称
- javaType:该字段映射的javaType,用来指定判断策略 
- case子标签:当column字段的值符合value属性时,使用resultType或者resultMap指定的映射关系。 
4.5. INSERT返回主键
第一种方式:使用useGeneratedKeys
<insert id="insertAndGetId" useGeneratedKeys="true" keyProperty="userId" parameterType="com.chenzhou.mybatis.User">
    insert into user(userName,password,comment)
    values(#{userName},#{password},#{comment})
</insert>
- useGeneratedKeys:默认false,true启用主键自增
- keyProperty:把主键自增后的id写入parameterType实体类的哪个属性里。
- MySQL和SQLServer支持,Oracle不支持。
第二种方式:使用<selectKey>获取自增id
<insert id="insertProduct" parameterType="domain.model.ProductBean" >
    <selectKey resultType="java.lang.Long" order="AFTER" keyProperty="productId">
        SELECT LAST_INSERT_ID()
    </selectKey>
    INSERT INTO t_product(productName,productDesrcible,merchantId)values(#{productName},#{productDesrcible},#{merchantId});
</insert>
- SELECT LAST_INSERT_ID()是查询刚刚插入的记录自增长的id的SQL语句
- order:BEFORE或AFTER,表示<selectKey>在insert语句的之前还是之后执行
- keyProperty:把主键自增后的id写入parameterType实体类的哪个属性里。
- resultType:指定查询结果在java里的返回类型
第三种方式:使用<selectKey>生成自定义id
<insert id="insertProduct" parameterType="domain.model.ProductBean" >
    <selectKey resultType="java.lang.String" order="BEFORE" keyProperty="productId">
        SELECT product_seq.nextval from dual
    </selectKey>
    INSERT INTO t_product(productId,productName,productDesrcible,merchantId)values(#{productId},#{productName},#{productDesrcible},#{merchantId});
</insert>
5. 动态SQL
5.1. if标签
<select id="findUserList" parameterType="user" resultType="user">
    select * from user where 1=1 
    <if test="id!=null and id!=''">
        and id=#{id}
    </if>
    <if test="username!=null and username!=''">
        and username like '%${username}%'
    </if>
</select>
注意:要做不等于空字符串校验。
5.2. choose(when,otherwise)标签
<select id="findUserInfoByOneParam" parameterType="Map" resultMap="UserInfoResult">
    select * from userinfo 
    <choose>
        <when test="searchBy=='department'">
            where department=#{department}
        </when>
        <when test="searchBy=='position'">
            where position=#{position}
        </when>
        <otherwise>
            where gender=#{gender}
        </otherwise>
    </choose>
</select>
按顺序判断when中的条件出否成立,如果有一个成立,则choose结束。当choose中所有when的条件都不满则时,则执行otherwise中的sql。
5.3. where/set/trim标签
where 标签:只会在至少有一个子元素的条件返回 SQL 子句的情况下才去插入“WHERE”子句。而且,若语句的开头为“AND”或“OR”,where标签也会将它们去除。
set标签:只会在至少有一个子元素的条件返回 SQL 子句的情况下才去插入“SET”子句。而且,若语句的结尾为,,set标签也会将它们去除。
trim标签:
- prefix:需要加在内部sql前面的前缀
- prefixoverride:指定需要去掉的内部sql的第一个前缀
- suffix:需要加在内部sql后面的后缀
- suffixoverride:指定需要去掉的内部sql的最后一个后缀
5.4. sql片段
sql标签:定义一个可以被反复使用的sql片段。属性id指定sql片段的唯一标识
include标签:引用一个sql片段。属性refid指定sql片段的id。通过namespace.片段id来引用其他xml内的片段
5.5. foreach标签
collection:指定pojo的集合属性名,如果parameterType是List类型时填“list”,是数组时填“array”
item:定义每次遍历生成的对象名,当为Map时指代值
index:定义遍历的索引名,当为Map时指代键
open:开始遍历时拼接的字符串
close:结束遍历时拼接的字符串
separator:便利对象之间拼接的字符串