一、Mybatis的Dao层实现

1.1 传统开发方式

1.1.1编写UserDao接口
public interface UserDao {
    List<User> findAll() throws IOException;
}
1.1.2.编写UserDaoImpl实现
public class UserDaoImpl implements UserDao {
    public List<User> findAll() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        List<User> userList = sqlSession.selectList("userMapper.findAll");
        sqlSession.close();
        return userList;
    }
}
1.1.3 测试传统方式
@Test
public void testTraditionDao() throws IOException {
    UserDao userDao = new UserDaoImpl();
    List<User> all = userDao.findAll();
    System.out.println(all);
}

1.2 代理开发方式

1.2.1 代理开发方式介绍

采用 Mybatis 的代理开发方式实现 DAO 层的开发,这种方式是企业开发的主流。

Mapper 接口开发方法只需要程序员编写Mapper 接口(相当于Dao 接口),由Mybatis 框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法。

Mapper 接口开发需要遵循以下规范:(重要)

1) Mapper.xml文件中的namespace与mapper接口的全限定名相同

2) Mapper接口方法名和Mapper.xml中定义的每个statement的id相同

3) Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同

4) Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同

1.2.2 编写UserMapper接口

Mybatis 之二 深入了解映射文件和核心配置文件(包含动态sql和分页插件)_xml
上面的红框处要一一对应

1.2.3 测试代理方式
@Test
public void testProxyDao() throws IOException {
    InputStream resourceAsStream = Resources.getResourceAsStream("SqlMapConfig.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //获得MyBatis框架生成的UserMapper接口的实现类  cn.kinggm520.dao.UserMapper
  	UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    User user = userMapper.findById(1);
    System.out.println(user);
    sqlSession.close();
}

1.3 知识小结

MyBatis的Dao层实现的两种方式:

手动对Dao进行实现:传统开发方式

代理方式对Dao进行实现:

 UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

1.4 主配置文件深入

1) 映射文件统一配置
<mappers>
    <!--
		<mapper resource="cn/kinggm520/UserMapper.xml"/>
		<mapper resource="cn/kinggm520/RoleMapper.xml"/> 每个文件单独配置,很麻烦
 		注意:
		1. package标签配置的是路径或包名
				name="cn/kinggm520/dao"  正确
				name="cn.kinggm520.dao"  正确
		2. 使用package配置方式,映射配置文件名称,必须和接口名称完全一致.否则解析错误
		3. 下面两种方式都可以
	-->
    <package name="cn/kinggm520/dao"></package>
    <package name="cn.kinggm520.dao"></package>   
</mappers>
2) 代理方式实现CRUD步骤
Mybaits底层使用动态代理方式,帮我们实现了接口的方法,因此,我们必须严格按照步骤开发
环境搭建的注意事项:
		第一个:创建UserMapper.xml 和 创建UserMapper.java时名称保持一致。
			在Mybatis中它把持久层的操作接口名称和映射文件也叫做:Mapper
			所以:UserDao 和 UserMapper是一样的
		第二个:在idea中创建目录的时候,它和包是不一样的
			包在创建时:cn.kinggm520.dao它是三级结构  ✔
			目录在创建时:cn.kinggm520.dao是一级目录  ✖
			可以直接创建目录: ✔✔✔
					cn/kinggm520/dao/UserDao.xml
					
		第三个:mybatis的映射配置文件位置必须和dao接口的包结构相同
		第四个:映射配置文件的mapper标签namespace属性的取值必须是dao接口的全限定类名
		第五个:映射配置文件的操作配置(select),id属性的取值必须是dao接口的方法名
		第六个: resultType输出类型: 输出类型是接口的方法的返回值类型.
					如果接口中有返回值,必须有输出类型resultType. 
		第七个: parameterType 参数类型:
					如果接口中有查询参数, 必须有参数类型. 

二、MyBatis映射文件深入

2.1 动态sql语句

2.1.1动态sql语句概述

Mybatis 的映射文件中,前面我们的 SQL 都是比较简单的,有些时候业务逻辑复杂时,我们的 SQL是动态变化的,此时在前面的学习中我们的 SQL 就不能满足要求了。

参考官方文档,描述如下:
Mybatis 之二 深入了解映射文件和核心配置文件(包含动态sql和分页插件)_分页_02

2.1.2 传统方式开发

传统方式开发, 对条件查询SQL拼接

/**
	传统方式
		1. 编写恒等SQL语句 select * from user  where 1 = 1 
		2. 判断查询条件, 拼接AND子查询语句  
		此代码出现在Dao实现类中
*/
public List<User> findByCondition(String username,String address) {
        String sql = "select * from user  where 1 = 1 ";
        StringBuilder sb = new StringBuilder(sql);
        //定义参数的集合
        List<Object> params = new ArrayList<Object>();
        if(username != null){
            sb.append(" and username like ?");
            params.add("%"+username+"%");
        }
        if(address != null){
            sb.append(" and address like ?");
            params.add("%"+address+"%");
        }
        //添加分页查询参数值
        sql = sb.toString();

        return template.query(sql,new BeanPropertyRowMapper<User>(User.class),params.toArray());
    }
2.1.3 动态SQL之<if>标签

Mybatis 提供的标签, 可以根据实体类的不同取值,使用不同的SQL语句来进行查询.比如在id如果不为空时,可以根据id查询. 如果username不为空时,还要加入用户名作为条件. 这种情况在我们的多条件组合查询中经常会遇到.

1. 在持久层接口中添加查询方法

List<User> findByCondition(User user);

2. 在用户的映射配置文件中配置

<select id="findByCondition" resultType="user" parameterType="user">
    select * from user where 1 = 1
    <if test="username !=null and username !=''">
        and username like #{username}
    </if>
     <if test="password !=null  ">
         and password like #{password}
    </if>
</select>
<!--
	注意: <if>标签test属性写的是对象的属性名, 如果是包装类对象要使用OGNL表达式的写法.
	即: userName是对象的属性,严格区分大小写,和数据库字段无关.
-->

3. 添加测试类中测试方法

   @Test
    public void findByCondition() {
        // 模拟查询条件
        User user = new User();
        user.setUsername("%张%");
        user.setPassword("%123%");

        UserDao dao = sqlSession.getMapper(UserDao.class);
        List<User> users = dao.findByCondition(user);
        for (User user1 : users) {
            System.out.println(user1);
        }
    }
2.1.4 动态SQL之<where>标签
为了简化上面的 where 1 = 1 的条件拼接,我们可以采用<where>标签来简化开发。

在用户的映射配置文件中配置

<select id="findByCondition" resultType="user" parameterType="user">
        SELECT * from user
        <where>
            <if test="username !=null">
                and username like #{username}
            </if>

            <if test="password !=null">
                and password like #{password}
            </if>
        </where>
    </select>
<!--
	注意: <if>标签test属性写的是对象的属性名, 如果是包装类对象要使用OGNL表达式的写法.
	即: userName是对象的属性,严格区分大小写,和数据库字段无关.
-->

使用<where>标签 查询和**<if>标签** 生成的SQL对比

<if>标签:
	select * from user where 1 = 1 and username like ? and sex = ? 
	
<where>标签:
	select * from user WHERE username like ? and sex = ?

总结:
	1. 如果条件都不成立,生成的SQL语句就没有 where 子语句了.
	2. where标签等同于"恒等式",在生成SQL时智能判断,若是第一个条件,自动将其and 或 or 关键字去掉
2.1.5 动态 SQL 之<foreach>

循环执行sql的拼接操作,例如:SELECT * FROM USER WHERE id IN (1,2,5)。

手动拼接SQL方式实现

package cn.kinggm520.test;

public class Test {
    // select * from user where id in(1,2,5)
    public static void main(String[] args) {
        //代表遍历集合的每个元素,生成的变量名   item
        Integer[] ids = {1, 2, 5};
        // sql
        String sql = " select * from user ";
        //语句的开始部分
        String open = " where id in(";
        //代表结束部分
        String close = ")";
        // 代表分隔符
        String sperator = ",";

        // 拼接SQL
        String s = "";
        for (int i = 0; i < ids.length; i++) {
            if (i < ids.length - 1) {
                s += ids[i] + sperator;
            } else {
                s += ids[i];
            }
        }
        System.out.println(sql + open + s + close);
    }
}
1 List集合参数查询

1. 在持久层UserMapper接口添加方法

 List<User> findUserInIds(List<Integer> ids);

2. 持久层UserMapper映射配置

映射文件

  <!--
        直接传递集合查询:
            parameterType="list", 其中list表示List类型.
    -->
<select id="findUserInIds" resultType="user" parameterType="list">
    select * from user 
    <where>
        <if test="list!=null and list.size()>0">
            <foreach collection="list" open=" id in (" item="id" close=")" separator=",">
                #{id}
            </foreach>
        </if>
    </where>
</select>

 select * from user  where id in (1,2,5)

3. 编写测试方法

@Test
public void findUserInIds() throws IOException {
    List<Integer> ids = new ArrayList<Integer>();
    ids.add(1);
    ids.add(2);
    ids.add(4);
    List<User> users = mapper.findUserInIds2(ids);
    for (User u : users) {
        System.out.println(u);
    }
}
2 数组参数查询

1. 在持久层UserMapper接口添加方法

 List<User> findUserInIds2(Integer[] arr);

2. 持久层Dao映射配置

映射文件

<!--
        直接传递数组查询:
                parameterType="list", 其中list表示List类型.(固定值,可以接受集合和数组)
                collection="array" 其中array: 指定参数类型为数组(固定值)
    -->
<select id="findUserInIds2" resultType="user" parameterType="list">
    select * from user
    <where>
        <if test="array!=null and array.length>0">
            <foreach collection="array" open="and id in (" item="id" close=")" separator=",">
                #{id}
            </foreach>
        </if>
    </where>
</select>

3. 编写测试方法

@Test
public void findUserInIds2() throws IOException {
    Integer[] arr = {41,45,46};
    List<User> users = mapper.findUserInIds2(arr);
    for (User u : users) {
        System.out.println(u);
    }
}

foreach标签的属性含义如下:

标签用于遍历集合,它的属性:

•collection:代表要遍历的集合或数组元素,注意编写时不要写#{}

•open:代表语句的开始部分

•close:代表结束部分

•item:代表遍历集合的每个元素,生成的变量名

•sperator:代表分隔符

2.1.6 动态 SQL 之<set>

用于动态更新语句的类似解决方案叫做 set

<update id="update" parameterType="user">
    update user set
    <if test="username!=null">
        username = #{username},
    </if>
    <if test="password!=null">
        password = #{password}
    </if>
    where id=#{id}
</update>
@Test
public void update() {
    User user = new User();
    user.setId(22);
    user.setUsername("呵呵");
    //user.setPassword("1234");

    mapper.update(user);
}

使用< set>标签完成更新操作

<!--更新用户-->
<update id="update" parameterType="user">
    update user
    <set>
        <if test="username!=null">
            username = #{username},
        </if>
        <if test="password!=null">
            password = #{password},
        </if>
    </set>
    where id=#{id}
</update>

对比 普通方式 和set标签

普通方式 set标签
最后一个更新项不能加逗号 每个更新项后面都加一个逗号

2.2 SQL片段抽取

Sql 中可将重复的 sql 提取出来,使用时用 include 引用即可,最终达到 sql 重用的目的

<!--抽取sql片段简化编写-->
<sql id="selectUser">select * from User</sql>
<select id="findById" parameterType="int" resultType="user">
    <include refid="selectUser"></include> where id=#{id}
</select>
<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>

2.3 知识小结

MyBatis映射文件配置:

<select>:查询
<insert>:插入
<update>:修改
<delete>:删除
<where>:where条件
<set>: 更新项
<if>:if判断
<foreach>:循环
<sql>:sql片段抽取

三、MyBatis核心配置文件深入

3.1 typeHandlers标签 – 了解

无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器(截取部分)。

Mybatis 之二 深入了解映射文件和核心配置文件(包含动态sql和分页插件)_xml_03

你可以重写类型处理器或创建自己的类型处理器来处理不支持的或非标准的类型。具体做法为:实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个类 org.apache.ibatis.type.BaseTypeHandler, 然后可以选择性地将它映射到一个JDBC类型。例如需求:一个Java中的Date数据类型,我想将之存到数据库的时候存成一个1970年至今的毫秒数,取出来时转换成java的Date,即java的Date与数据库的varchar毫秒值之间转换。

开发步骤:

①定义转换类继承类BaseTypeHandler

②覆盖4个未实现的方法,其中setNonNullParameter为java程序设置数据到数据库的回调方法,getNullableResult为查询时 mysql的字符串类型转换成 java的Type类型的方法

③在MyBatis核心配置文件中进行注册

测试转换是否正确

public class MyDateTypeHandler extends BaseTypeHandler<Date> {
    //对象类型 --> 数据库类型
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, Date date, JdbcType type) {
         preparedStatement.setLong(i, parameter.getTime());
    }
    // 数据库类型 --> 对象类型
    public Date getNullableResult(ResultSet res, String columnName) throws SQLException {
       long time = rs.getLong(columnName);
        return new Date(time);
    }
     // 数据库类型 --> 对象类型
    public Date getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
       long time = rs.getLong(columnIndex);
        return new Date(time);
    }
     // 数据库类型 --> 对象类型
    public Date getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        long time = cs.getLong(columnIndex);
        return new Date(time);
    }
}
<!--注册类型自定义转换器-->
<typeHandlers>
    <typeHandler handler="cn.kinggm520.typeHandlers.MyDateTypeHandler"></typeHandler>
</typeHandlers>

测试添加操作:

user.setBirthday(new Date());
userMapper.add2(user);

数据库数据:

Mybatis 之二 深入了解映射文件和核心配置文件(包含动态sql和分页插件)_xml_04
测试查询操作:
Mybatis 之二 深入了解映射文件和核心配置文件(包含动态sql和分页插件)_分页_05

3.2 plugins标签

MyBatis可以使用第三方的插件来对功能进行扩展,分页助手PageHelper是将分页的复杂操作进行封装,使用简单的方式即可获得分页的相关数据

开发步骤:

①导入通用PageHelper的坐标

②在mybatis核心配置文件中配置PageHelper插件

③测试分页数据获取

①导入通用PageHelper坐标
<!-- 分页助手 -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.2</version>
</dependency>
<dependency>
    <groupId>com.github.jsqlparser</groupId>
    <artifactId>jsqlparser</artifactId>
    <version>0.9.1</version>
</dependency>

②在mybatis核心配置文件中配置PageHelper插件

注意< plugins>标签要配在< environments>标签上面

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <!--选择数据库方言-->
        <property name="helperDialect" value="mysql"/>
        <!--reasonable:默认值是false
           true:自动处理首页和尾页,如果是第一页,再点上一页还是第一页不需要在前端去做是否为首页的判断
           如果是尾页,再点下一页还是最后一页,也不需要在前端做是否为尾页的判断
            -->
        <property name="reasonable" value="true"/>
    </plugin>
</plugins>
③ 修改映射配置文件
 <!--查询所有-->
    <select id="findAll" resultType="user">
        select * from user
    </select>

④测试分页代码实现
//    测试分页
    @Test
    public void test7(){
        //        设置分页参数
        PageHelper.startPage(1,3);

        SqlSession sqlSession = factory.openSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);


        List<User> users= mapper.pageQuery();

        System.out.println(users);

        for (User user : currentPage) {
            System.out.println(user);
        }

    }

获得分页相关的其他参数

//其他分页的数据
PageInfo<User> pageInfo = new PageInfo<User>(users);
System.out.println("总条数:"+pageInfo.getTotal());
System.out.println("总页数:"+pageInfo.getPages());
System.out.println("当前页:"+pageInfo.getPageNum());
System.out.println("每页显示长度:"+pageInfo.getPageSize());

注意:
List< User> users = mapper.findAll();
System.out.println(users );
Mybatis 之二 深入了解映射文件和核心配置文件(包含动态sql和分页插件)_分页_06

3.3 知识小结

MyBatis核心配置文件常用标签:

1、properties标签:该标签可以加载外部的properties文件

2、typeAliases标签:设置类型别名 – 重点

3、environments标签:数据源环境配置标签

4、typeHandlers标签:配置自定义类型处理器

5、plugins标签:配置MyBatis的插件 – 重点

四、扩展Mybatis多条件查询的实现方式 参考文章

4.1多个条件来查询用户方案(1)

public Users findNameAndPwd_Map(Map<String,Object> mp)throws RuntimeException;

通过在Map集合中封装条件作为参数来去查询

<select id="findNameAndPwd_Map" resultType="user" parameterType="java.util.Map">
  select 
  * 
  from 
  users
  where
  uname=#{pn}
  and upwd=#{pd}    
</select>

上面的pn,pd为map的键,对应值为条件

Map<String,Object> m1 = new HashMap<String,Object>();
m1.put("pn", "张三");
m1.put("pd", "00");
Users tu = udao.findNameAndPwd_Map( m1 );
System.out.println("多个参数:Map\t"+tu);

4.2多个条件来查询用户方案(2)

public Users findNameAndPwd_Users(Users mp)throws RuntimeException;

通过在一个Users对象中封装条件来查询

{}中的参数即为对应的get方法 首字母小写

<select id="findNameAndPwd_Users" resultType="user" parameterType="user">
  select 
  * 
  from 
  users
  where
  uname=#{uname}
  and upwd=#{upwd}  
</select>

4.3多个条件来查询用户方案(3)

通过调用方法中参数的序列号来给定参数

public Users findNameAndPwd_param(String name,String pwd)throws RuntimeException;

{}中为方法参数的下标

<select id="findNameAndPwd_param" resultType="user">
  select 
  * 
  from 
  users
  where
  uname=#{arg0}
  and upwd=#{arg1} 
</select>

测试

tu = udao.findNameAndPwd_param( "张三","00" );
System.out.println("多个参数:方法参数下标\t"+tu);

4.4多个条件来查询用户方案(4)

通过接口中给参数定义注解

public Users findNameAndPwd_anno(@Param("name1") String name, @Param("pwd1") String pwd)throws RuntimeException;

{}中即为注解的名称

<select id="findNameAndPwd_anno" resultType="user">
  select 
  * 
  from 
  users
  where
  uname=#{name1}
  and upwd=#{pwd1}
</select>

测试

tu = udao.findNameAndPwd_anno( "张三","000" );
System.out.println("多个参数:注解\t"+tu);