Mybatis 介绍

ORM - Object Relational Mapping
- Hibernate - 完全的ORM - 通过对象操作
- MyBatis - 基于sql语句 - ORM

MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
MyBatis是一个优秀的持久层框架,它对jdbc的操作数据库的过程进行封装,使开发者只需要关注SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。
Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。

使用jdbc编程问题总结

创建mysql数据库

创建数据库,将资料中的mybatis.sql脚本导入到数据库中。

导入后效果如下图:
image.png

创建工程

开发环境:
IDE:eclipse Mars2
JDK:1.8

1、创建一个web工程。
按下图进行创建
image.png
2、需要mysql 的数据库驱动,在资料中找到jar包。
image.png

jdbc编程步骤:

1、 加载数据库驱动
2、 创建并获取数据库链接
3、 创建jdbc statement对象
4、 设置sql语句
5、 设置sql语句中的参数(使用PreparedStatement)
6、 通过statement执行sql并获取结果
7、 对sql执行结果进行解析处理
8、 释放资源(resultSet、preparedstatement、connection)

jdbc程序

新建包com.igeek
创建测试类MybatisTest

  1. public static void main(String[] args) {
  2. Connection connection = null;
  3. PreparedStatement preparedStatement = null;
  4. ResultSet resultSet = null;
  5. try {
  6. // 加载数据库驱动
  7. Class.forName("com.mysql.cj.jdbc.Driver");
  8. // 通过驱动管理类获取数据库链接
  9. connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "root");
  10. // 定义sql语句 ?表示占位符
  11. String sql = "select * from user where username = ?";
  12. // 获取预处理statement
  13. preparedStatement = connection.prepareStatement(sql);
  14. // 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
  15. preparedStatement.setString(1, "王五");
  16. // 向数据库发出sql执行查询,查询出结果集
  17. resultSet = preparedStatement.executeQuery();
  18. // 遍历查询结果集
  19. while (resultSet.next()) {
  20. System.out.println(resultSet.getString("id") + " " + resultSet.getString("username"));
  21. }
  22. } catch (Exception e) {
  23. e.printStackTrace();
  24. } finally {
  25. // 释放资源
  26. if (resultSet != null) {
  27. try {
  28. resultSet.close();
  29. } catch (SQLException e) {
  30. // TODO Auto-generated catch block
  31. e.printStackTrace();
  32. }
  33. }
  34. if (preparedStatement != null) {
  35. try {
  36. preparedStatement.close();
  37. } catch (SQLException e) {
  38. // TODO Auto-generated catch block
  39. e.printStackTrace();
  40. }
  41. }
  42. if (connection != null) {
  43. try {
  44. connection.close();
  45. } catch (SQLException e) {
  46. // TODO Auto-generated catch block
  47. e.printStackTrace();
  48. }
  49. }
  50. }
  51. }

上边使用jdbc的原始方法(未经封装)实现了查询数据库表记录的操作。

jdbc问题总结如下:

JDBC操作数据库的流程:
A:准备数据库连接参数
B:连接数据库
C:编译SQL
D:设置SQL参数
E:执行SQL获取结果
F:遍历结果集,封装数据到JavaBean
G:关闭资源

出现的问题:

  1. 数据库连接参数硬编码到Java代码中,如果数据库环境需要改变,需要重新编译,不好!

    1. 解决:把数据库连接参数写到外部配置文件,通过配置文件加载参数信息、注解
  2. SQL语句硬编码到Java代码中,业务变更、SQL优化都需要修改SQL语句,又需要重新编译,不好!

    1. 解决:把SQL放到外部配置文件,通过加载配置文件,解析SQL语句
  3. SQL参数问题因为把SQL放到外部配置中,用代码加载解析,那么就引发以下新问题:

    1. 需要自动判断参数类型
    2. 需要自动判断参数位置对应关系
    3. 解决方案:如何自动判断参数类型和位置呢,待定?
  4. 遍历结果集非常麻烦,问题:

    1. 需要自己判断结果字段类型
    2. 需要自己明确结果字段的名称
    3. 需要手动封装结果字段到JavaBean中
    4. 如果有集合还得自己封装对象到集合中
    5. 解决:希望可以自动把结果集封装到JavaBean,如果有多行,可以自动封装到集合
  5. 频繁打开和创建数据库连接,比较浪费资源,效率低

    1. 解决:采用数据库连接池

SSH
SSM
上面的这些问题,我们自己实现起来比较繁琐,现有的持久层框架中,Hibernate也无法解决所有问题,而且Hibernate的学习成本比较高,在面对复杂查询时,查询效率略低。

我们需要新的技术来解决这些问题:Mybatis

Mybatis概述

简介

image.png

官网

官网:http://www.mybatis.org/mybatis-3/
中文官方文档:http://www.mybatis.org/mybatis-3/zh/index.html
image.png

特点

Mybatis:
1) 支持自定义SQL、存储过程、及高级映射
2) 实现自动对SQL的参数设置
3) 实现自动对结果集进行解析和封装
4) 通过XML或者注解进行配置和映射
实现Java对象与数据库表的映射转换

可以发现,MyBatis是对JDBC进行了简单的封装,帮助用户进行SQL参数的自动化映射,以及结果集与Java对象的映射。与Hibernate相比,更加配置简单、灵活、执行效率高。但是正因为此,所以没有实现完全自动化,需要手写SQL,这是优点也是缺点。

因此,对性能要求较高的电商类项目,一般会使用MyBatis,而对与业务逻辑复杂,不太在乎执行效率的传统行业,一般会使用Hibernate

架构

image.png
MyBatis架构总结:

  1. MyBatis有两类配置文件:
    1. mybatis-condig.xml,是MyBatis的全局配置文件,包含全局配置信息,如数据库连接参数、插件等。整个框架中只需要一个即可。
    2. xxxMapper.xml,是映射文件,里面配置要执行的SQL语句,每个SQL对应一个Statement,可以有多个Mapper.xml文件
  2. 首先会通过SqlSessionFactoryBuilder来加载配置文件,生成一个SqlSessionFactory
    1. 会加载mybatis-config.xml和mapper.xml
    2. 加载mapper.xml的时候,顺便会对Sql进行编译,形成statement
  3. 通过SqlSessionFactory建立连接,获取SqlSession对象
  4. MyBatis获取要执行的statement,进行自动参数设置
  5. SqlSession底层会通过Executor(执行器)来执行编译好的Statement,获取结果
  6. SQL的输入参数类型:
    1. POJO,普通Java对象
    2. HashMap,其实是POJO的Map形式, 键值对就是对象字段名和值
    3. 各种基本数据类型
  7. 查询结果的输出形式
    1. POJO,普通Java对象
    2. HashMap,其实是POJO的Map形式, 键值对就是对象字段名和值
    3. 各种基本数据类型

      Mybatis入门程序

      mybatis下载

      mybaits的代码由github.com管理
      下载地址:https://github.com/mybatis/mybatis-3/releases
      课前资料提供的mybatis如下:
      image.png
      mybatis-3.2.7.jar mybatis的核心包
      lib文件夹 mybatis的依赖包所在
      mybatis-3.2.7.pdf mybatis使用手册

基于maven的引入

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.6</version>
</dependency>

业务需求

使用MyBatis实现以下功能:
根据用户id查询一个用户
根据用户名称模糊查询用户列表
添加用户
更新用户
删除用户

环境搭建

创建web项目

mybatis_d01_c05
创建包com.igeek
创建类MybatisTest
如下图
image.png

加入jar包

加入mybatis核心包、依赖包、数据驱动包。
mybatis核心包
image.png
mybatis依赖包
image.png
数据库驱动包
image.png
效果:
image.png

加入配置文件

如下图创建资源文件夹config,加入log4j.properties和SqlMapConfig.xml配置文件
image.png
image.png

log4j配置文件

在resource目录下面创建log4j2.xml 如下:

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </Console>
    </Appenders>

    <Loggers>
        <Root level="error" >
            <AppenderRef ref="Console" />
        </Root>

        <logger name="test" level="debug" additivity="false">
            <AppenderRef ref="Console" />
        </logger>

        <logger name="com.igeek.servlet_bili.service" level="debug" additivity="false">
            <AppenderRef ref="Console" />
        </logger>

    </Loggers>

</Configuration>

或者 创建log4j.properties如下:

# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

mybatis默认使用log4j作为输出日志信息。

SqlMapConfig.xml

在config下创建SqlMapConfig.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">
<configuration>
    <!-- 和spring整合后 environments配置将废除 -->
    <environments default="development">
        <environment id="development">
            <!-- 使用jdbc事务管理 -->
            <transactionManager type="JDBC" />
            <!-- 数据库连接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url"
                    value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8" />
                <property name="username" value="root" />
                <property name="password" value="root" />
            </dataSource>
        </environment>
    </environments>
</configuration>

SqlMapConfig.xml是mybatis核心配置文件,配置文件内容为数据源、事务管理。

效果

image.png

创建pojo

pojo类作为mybatis进行sql映射使用,po类通常与数据库表对应,
数据库user表如下图:
image.png
User.java如下:

public class User {
    private int id;
    private String username;// 用户姓名
    private String sex;// 性别
    private Date birthday;// 生日
    private String address;// 地址


//get/set……
}

sql映射文件

在config下的sqlmap目录下创建sql映射文件User.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">
<!-- namespace:命名空间,用于隔离sql,还有一个很重要的作用,后面会讲 -->
<mapper namespace="test">
</mapper>

加载映射文件

mybatis框架需要加载Mapper.xml映射文件
将users.xml添加在SqlMapConfig.xml,如下:

<mappers>
    <mapper resource="sqlmap/User.xml"/>
</mappers>

image.png

实现根据id查询用户

使用的sql:
SELECT * FROM user WHERE id = 1

映射文件:

在user.xml中添加select标签,编写sql:

<?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">
<!-- namespace:命名空间,用于隔离sql,还有一个很重要的作用,后面会讲 -->
<mapper namespace="test">

    <!-- id:statement的id 或者叫做sql的id-->
    <!-- parameterType:声明输入参数的类型 -->
    <!-- resultType:声明输出结果的类型,应该填写pojo的全路径 -->
    <!-- #{}:输入参数的占位符,相当于jdbc的? -->
    <select id="queryUserById" parameterType="int" resultType="com.igeek.pojo.User">
        SELECT * FROM `user` WHERE id  = #{id}
    </select>

</mapper>

测试程序:

测试程序步骤:

  1. 创建SqlSessionFactoryBuilder对象
  2. 加载SqlMapConfig.xml配置文件
  3. 创建SqlSessionFactory对象
  4. 创建SqlSession对象
  5. 执行SqlSession对象执行查询,获取结果User
  6. 打印结果
  7. 释放资源

MybatisTest编写测试程序如下:

public class MybatisTest {
    private SqlSessionFactory sqlSessionFactory = null;

    @Before
    public void init() throws Exception {
        // 1. 创建SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();

        // 2. 加载SqlMapConfig.xml配置文件
        InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");

        // 3. 创建SqlSessionFactory对象
        this.sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
    }

    @Test
    public void testQueryUserById() throws Exception {
        // 4. 创建SqlSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 5. 执行SqlSession对象执行查询,获取结果User
        // 第一个参数是User.xml的statement的id,第二个参数是执行sql需要的参数;
        Object user = sqlSession.selectOne("queryUserById", 1);

        // 6. 打印结果
        System.out.println(user);

        // 7. 释放资源
        sqlSession.close();
    }
}

效果

测试结果如下图
image.png

实现根据用户名模糊查询用户

查询sql:
SELECT * FROM user WHERE username LIKE ‘%王%’

方法一

映射文件

在User.xml配置文件中添加如下内容:

<!-- 如果返回多个结果,mybatis会自动把返回的结果放在list容器中 -->
<!-- resultType的配置和返回一个结果的配置一样 -->
<select id="queryUserByUsername1" parameterType="string" resultType="com.igeek.pojo.User">
        SELECT * FROM `user` WHERE username LIKE #{username}
</select>

测试程序

MybatisTest中添加测试方法如下:

@Test
public void testQueryUserByUsername1() throws Exception {
    // 4. 创建SqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 5. 执行SqlSession对象执行查询,获取结果User
    // 查询多条数据使用selectList方法
    List<Object> list = sqlSession.selectList("queryUserByUsername1", "%王%");

    // 6. 打印结果
    for (Object user : list) {
        System.out.println(user);
    }

    // 7. 释放资源
    sqlSession.close();
}

效果

测试效果如下图:
image.png

方法二

映射文件:

在User.xml配置文件中添加如下内容:

<!-- 如果传入的参数是简单数据类型,${}里面必须写value -->
<select id="queryUserByUsername2" parameterType="string" resultType="com.igeek.pojo.User">
  SELECT * FROM `user` WHERE username LIKE '%${value}%'
</select>

测试程序:

MybatisTest中添加测试方法如下:

@Test
public void testQueryUserByUsername2() throws Exception {
    // 4. 创建SqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 5. 执行SqlSession对象执行查询,获取结果User
    // 查询多条数据使用selectList方法
    List<Object> list = sqlSession.selectList("queryUserByUsername2", "王");

    // 6. 打印结果
    for (Object user : list) {
        System.out.println(user);
    }

    // 7. 释放资源
    sqlSession.close();
}

效果

测试结果如下图:
image.png

小结

#{}和${}

{}表示一个占位符号,通过#{}可以实现preparedStatement向占位符中设置值,自动进行java类型和jdbc类型转换。#{}可以有效防止sql注入。 #{}可以接收简单类型值或pojo属性值。 如果parameterType传输单个简单类型值,#{}括号中可以是value或其它名称。
${}表示拼接sql串,通过${}可以将parameterType 传入的内容拼接在sql中且不进行jdbc类型转换,${}可以接收简单类型值或pojo属性值,如果parameterType传输单个简单类型值,${}括号中只能是value。

parameterType和resultType

parameterType:指定输入参数类型,mybatis通过ognl从输入对象中获取参数值拼接在sql中。
resultType:指定输出结果类型,mybatis将sql查询结果的一行记录数据映射为resultType指定类型的对象。如果有多条数据,则分别进行映射,并把对象放到容器List中

selectOne和selectList

selectOne查询一条记录,如果使用selectOne查询多条记录则抛出异常:
org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 3
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:70)
selectList可以查询一条或多条记录。

实现添加用户

使用的sql:
INSERT INTO user (username,birthday,sex,address) VALUES (‘黄忠’,’2016-07-26’,’1’,’三国’)

映射文件:

在User.xml配置文件中添加如下内容:

<!-- 保存用户 -->
<insert id="saveUser" parameterType="com.igeek.pojo.User">
  INSERT INTO `user`
  (username,birthday,sex,address) VALUES (#{username},#{birthday},#{sex},#{address})
</insert>

测试程序

MybatisTest中添加测试方法如下:

@Test
public void testSaveUser() {
    // 4. 创建SqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 5. 执行SqlSession对象执行保存
    // 创建需要保存的User
    User user = new User();
    user.setUsername("张飞");
    user.setSex("1");
    user.setBirthday(new Date());
    user.setAddress("蜀国");

    sqlSession.insert("saveUser", user);
    System.out.println(user);

    // 需要进行事务提交
    sqlSession.commit();

    // 7. 释放资源
    sqlSession.close();
}

效果

image.png
如上所示,保存成功,但是id=0,需要解决id返回不正常的问题。

mysql自增主键返回

查询id的sql
SELECT LAST_INSERT_ID()
推荐使用 useGeneratedKeys
image.png

通过修改User.xml映射文件,可以将mysql自增主键返回:
如下添加selectKey 标签

<!-- 保存用户 -->
<insert id="saveUser" parameterType="com.igeek.pojo.User">
    <!-- selectKey 标签实现主键返回 -->
    <!-- keyColumn:主键对应的表中的哪一列 -->
    <!-- keyProperty:主键对应的pojo中的哪一个属性 -->
    <!-- order:设置在执行insert语句前执行查询id的sql,还是在执行insert语句之后执行查询id的sql -->
    <!-- resultType:设置返回的id的类型 -->
    <selectKey keyColumn="id" keyProperty="id" order="AFTER" resultType="int">
        SELECT LAST_INSERT_ID()
    </selectKey>
    INSERT INTO `user`
    (username,birthday,sex,address) VALUES
    (#{username},#{birthday},#{sex},#{address})
</insert>

LAST_INSERT_ID():是mysql的函数,返回auto_increment自增列新记录id值。

效果如下图所示:
image.png
返回的id为48,能够正确的返回id了。

Mysql使用 uuid实现主键

需要增加通过select uuid()得到uuid值

创建表

CREATE TABLE `user1` (
`id`  int(60) NULL DEFAULT NULL ,
`username`  varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名称' ,
`birthday`  date NULL DEFAULT NULL COMMENT '生日' ,
`sex`  char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '性别' ,
`address`  varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '地址' ,
`uuid`  varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL ,
PRIMARY KEY (`uuid`)
)
ENGINE=InnoDB
DEFAULT CHARACTER SET=utf8 COLLATE=utf8_general_ci
ROW_FORMAT=COMPACT
;
public class User {
    private int id;
    private String uuid;

    private String username;// 用户姓名
    private String sex;// 性别
    private Date birthday;// 生日
    private String address;// 地址
}
<!-- 保存用户 -->
<insert id="saveUser" parameterType="com.igeek.pojo.User">
    <!-- selectKey 标签实现主键返回 -->
    <!-- keyColumn:主键对应的表中的哪一列 -->
    <!-- keyProperty:主键对应的pojo中的哪一个属性 -->
    <!-- order:设置在执行insert语句前执行查询id的sql,在执行insert语句之后执行查询id的sql -->
    <!-- resultType:设置返回的id的类型 -->
    <selectKey keyColumn="uuid" keyProperty="uuid" order="BEFORE" resultType="string">
        SELECT uuid()
    </selectKey>
    INSERT INTO `user1`
    (username,birthday,sex,address,uuid) VALUES
    (#{username},#{birthday},#{sex},#{address},#{uuid})
</insert>

注意这里使用的order是“BEFORE”

修改用户

根据用户id修改用户名

使用的sql:
UPDATE user SET username = ‘赵云’ WHERE id = 26

映射文件

在User.xml配置文件中添加如下内容:

<!-- 更新用户 -->
<update id="updateUserById" parameterType="com.igeek.pojo.User">
    UPDATE `user` SET username = #{username} WHERE id = #{id}
</update>

测试程序

MybatisTest中添加测试方法如下:

@Test
public void testUpdateUserById() {
    // 4. 创建SqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 5. 执行SqlSession对象执行更新
    // 创建需要更新的User
    User user = new User();
    user.setId(26);
    user.setUsername("关羽");
    user.setSex("1");
    user.setBirthday(new Date());
    user.setAddress("蜀国");

    sqlSession.update("updateUserById", user);

    // 需要进行事务提交
    sqlSession.commit();

    // 7. 释放资源
    sqlSession.close();
}

效果

测试效果如下图:
image.png

删除用户

根据用户id删除用户

使用的sql
DELETE FROM user WHERE id = 47

映射文件:

在User.xml配置文件中添加如下内容:

<!-- 删除用户 -->
<delete id="deleteUserById" parameterType="int">
  delete from user where id=#{id}
</delete>

测试程序:

MybatisTest中添加测试方法如下:

@Test
public void testDeleteUserById() {
    // 4. 创建SqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();

    // 5. 执行SqlSession对象执行删除
    sqlSession.delete("deleteUserById", 48);

    // 需要进行事务提交
    sqlSession.commit();

    // 7. 释放资源
    sqlSession.close();
}

效果

测试效果如下图:
image.png

Mybatis解决jdbc编程的问题

1、 数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库连接池可解决此问题。
解决:在SqlMapConfig.xml中配置数据连接池,使用连接池管理数据库链接。
2、 Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。
3、 向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
解决:Mybatis自动将java对象映射至sql语句,通过statement中的parameterType定义输入参数的类型。
4、 对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
解决:Mybatis自动将sql执行结果映射至java对象,通过statement中的resultType定义输出结果的类型。

Dao开发方法

使用MyBatis开发Dao,通常有两个方法,即原始Dao开发方法和Mapper动态代理开发方法。

需求

使用MyBatis开发DAO实现以下的功能:
根据用户id查询一个用户信息
根据用户名称模糊查询用户信息列表
添加用户信息

SqlSession的使用范围

SqlSession中封装了对数据库的操作,如:查询、插入、更新、删除等。
SqlSession通过SqlSessionFactory创建。
SqlSessionFactory是通过SqlSessionFactoryBuilder进行创建。

SqlSessionFactoryBuilder

SqlSessionFactoryBuilder用于创建SqlSessionFacoty,SqlSessionFacoty一旦创建完成就不需要SqlSessionFactoryBuilder了,因为SqlSession是通过SqlSessionFactory创建的。所以可以将SqlSessionFactoryBuilder当成一个工具类使用,最佳使用范围是方法范围即方法体内局部变量。

SqlSessionFactory

SqlSessionFactory是一个接口,接口中定义了openSession的不同重载方法,SqlSessionFactory的最佳使用范围是整个应用运行期间,一旦创建后可以重复使用,通常以单例模式管理SqlSessionFactory。

SqlSession

SqlSession是一个面向用户的接口,sqlSession中定义了数据库操作方法。
每个线程都应该有它自己的SqlSession实例。SqlSession的实例不能共享使用,它也是线程不安全的。因此最佳的范围是请求或方法范围。绝对不能将SqlSession实例的引用放在一个类的静态字段或实例字段中。

打开一个SqlSession;使用完毕就要关闭它。通常把这个关闭操作放到 finally 块中以确保每次都能执行关闭。如下:

SqlSession session = sqlSessionFactory.openSession();
try {
     // do work
} finally {
    session.close();
}

原始Dao开发方式

原始Dao开发方法需要程序员编写Dao接口和Dao实现类。

映射文件

编写映射文件如下:(也可以使用入门程序完成的映射文件)

<?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">
<!-- namespace:命名空间,用于隔离sql,还有一个很重要的作用,后面会讲 -->
<mapper namespace="test">

    <!-- 根据id查询用户 -->
    <select id="queryUserById" parameterType="int"
        resultType="com.igeek.pojo.User">
        select * from user where id = #{id}
    </select>

    <!-- 根据username模糊查询用户 -->
    <select id="queryUserByUsername" parameterType="string"
        resultType="com.igeek.pojo.User">
        select * from user where username like '%${value}%'
    </select>

    <!-- 保存用户 -->
    <insert id="saveUser" parameterType="com.igeek.pojo.User">
        <selectKey keyProperty="id" keyColumn="id" order="AFTER"
            resultType="int">
            SELECT LAST_INSERT_ID()
        </selectKey>
        insert into user(username,birthday,sex,address)
        values(#{username},#{birthday},#{sex},#{address})
    </insert>

</mapper>

Dao接口

先进行DAO的接口开发,编码如下:

public interface UserDao {
    /**
     * 根据id查询用户
     * 
     * @param id
     * @return
     */
    User queryUserById(int id);

    /**
     * 根据用户名模糊查询用户
     * 
     * @param username
     * @return
     */
    List<User> queryUserByUsername(String username);

    /**
     * 保存用户
     * 
     * @param user
     */
    void saveUser(User user);
}

Dao实现类

编写的Dao实现类如下

public class UserDaoImpl implements UserDao {
    private SqlSessionFactory sqlSessionFactory;

    public UserDaoImpl(SqlSessionFactory sqlSessionFactory) {
        super();
        this.sqlSessionFactory = sqlSessionFactory;
    }

    @Override
    public User queryUserById(int id) {
        // 创建SqlSession
        SqlSession sqlSession = this.sqlSessionFactory.openSession();
        // 执行查询逻辑
        User user = sqlSession.selectOne("queryUserById", id);
        // 释放资源
        sqlSession.close();

        return user;
    }

    @Override
    public List<User> queryUserByUsername(String username) {
        // 创建SqlSession
        SqlSession sqlSession = this.sqlSessionFactory.openSession();

        // 执行查询逻辑
        List<User> list = sqlSession.selectList("queryUserByUsername", username);
        // 释放资源
        sqlSession.close();
        return list;
    }

    @Override
    public void saveUser(User user) {
        // 创建SqlSession
        SqlSession sqlSession = this.sqlSessionFactory.openSession();

        // 执行保存逻辑
        sqlSession.insert("saveUser", user);
        // 提交事务
        sqlSession.commit();
        // 释放资源
        sqlSession.close();
    }
}

Dao测试

创建一个JUnit的测试类,对UserDao进行测试,测试代码如下:

public class UserDaoTest {
    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void init() throws Exception {
        // 创建SqlSessionFactoryBuilder
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // 加载SqlMapConfig.xml配置文件
        InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        // 创建SqlsessionFactory
        this.sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
    }

    @Test
    public void testQueryUserById() {
        // 创建DAO
        UserDao userDao = new UserDaoImpl(this.sqlSessionFactory);
        // 执行查询
        User user = userDao.queryUserById(1);
        System.out.println(user);
    }

    @Test
    public void testQueryUserByUsername() {
        // 创建DAO

        UserDao userDao = new UserDaoImpl(this.sqlSessionFactory);
        // 执行查询
        List<User> list = userDao.queryUserByUsername("张");
        for (User user : list) {
            System.out.println(user);
        }
    }

    @Test
    public void testSaveUser() {
        // 创建DAO
        UserDao userDao = new UserDaoImpl(this.sqlSessionFactory);

        // 创建保存对象
        User user = new User();
        user.setUsername("刘备");
        user.setBirthday(new Date());
        user.setSex("1");
        user.setAddress("蜀国");
        // 执行保存
        userDao.saveUser(user);

        System.out.println(user);
    }
}

问题

原始Dao开发中存在以下问题:

  • Dao方法体存在重复代码:通过SqlSessionFactory创建SqlSession,调用SqlSession的数据库操作方法
  • 调用sqlSession的数据库操作方法需要指定statement的id,这里存在硬编码,不得于开发维护。

Mapper动态代理方式

开发规范

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的类型相同

    Mapper.xml(映射文件)

    定义mapper映射文件UserMapper.xml
    将UserMapper.xml放在config下mapper目录下,效果如下:
    image.png
    UserMapper.xml配置文件内容: ```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">

    select last_insert_id() insert into user(username,birthday,sex,address) values (#{username},#{birthday},#{sex},#{address});

<a name="u2k4T"></a>
#### UserMapper(接口文件)
创建UserMapper接口代码如下:
```java
public interface UserMapper {
    /**
     * 根据id查询
     * 
     * @param id
     * @return
     */
    User queryUserById(int id);

    /**
     * 根据用户名查询用户
     * 
     * @param username
     * @return
     */
    List<User> queryUserByUsername(String username);

    /**
     * 保存用户
     * 
     * @param user
     */
    void saveUser(User user);
}

加载UserMapper.xml文件

修改SqlMapConfig.xml文件,添加以下所示的内容:

<!-- 加载映射文件 -->
<mappers>
        <mapper resource="sqlmap/User.xml" />
    <mapper resource="mapper/UserMapper.xml" />
</mappers>

测试

编写的测试方法如下:

public class UserMapperTest {
    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void init() throws Exception {
        // 创建SqlSessionFactoryBuilder
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // 加载SqlMapConfig.xml配置文件
        InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        // 创建SqlsessionFactory
        this.sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
    }

    @Test
    public void testQueryUserById() {
        // 获取sqlSession,和spring整合后由spring管理
        SqlSession sqlSession = this.sqlSessionFactory.openSession();

        // 从sqlSession中获取Mapper接口的代理对象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        // 执行查询方法
        User user = userMapper.queryUserById(1);
        System.out.println(user);

        // 和spring整合后由spring管理
        sqlSession.close();
    }

    @Test
    public void testQueryUserByUsername() {
        // 获取sqlSession,和spring整合后由spring管理
        SqlSession sqlSession = this.sqlSessionFactory.openSession();

        // 从sqlSession中获取Mapper接口的代理对象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        // 执行查询方法
        List<User> list = userMapper.queryUserByUsername("张");
        for (User user : list) {
            System.out.println(user);
        }

        // 和spring整合后由spring管理
        sqlSession.close();
    }

    @Test
    public void testSaveUser() {
        // 获取sqlSession,和spring整合后由spring管理
        SqlSession sqlSession = this.sqlSessionFactory.openSession();

        // 从sqlSession中获取Mapper接口的代理对象
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        // 创建保存对象
        User user = new User();
        user.setUsername("刘备");
        user.setBirthday(new Date());
        user.setSex("1");
        user.setAddress("蜀国");
        // 执行查询方法
        userMapper.saveUser(user);
        System.out.println(user);


        // 和spring整合后由spring管理
        sqlSession.commit();
        sqlSession.close();
    }
}

小结

  • selectOne和selectList

动态代理对象调用sqlSession.selectOne()和sqlSession.selectList()是根据mapper接口方法的返回值决定,如果返回list则调用selectList方法,如果返回单个对象则调用selectOne方法。

  • namespace

mybatis官方推荐使用mapper代理方法开发mapper接口,程序员不用编写mapper接口实现类,使用mapper代理方法时,输入参数可以使用pojo包装对象或map对象,保证dao的通用性。

SqlMapConfig.xml配置文件

配置内容

SqlMapConfig.xml中配置的内容和顺序如下:
properties(属性)
settings(全局配置参数)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境集合属性对象)
environment(环境子属性对象)
transactionManager(事务管理)
dataSource(数据源)
mappers(映射器)

properties(属性)

SqlMapConfig.xml可以引用java属性文件中的配置信息如下:

在config下定义db.properties文件,如下所示:
image.png
db.properties配置文件内容如下:

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8
jdbc.username=root
jdbc.password=root

SqlMapConfig.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">
<configuration>
    <!-- 是用resource属性加载外部配置文件 -->
    <properties resource="db.properties">
        <!-- 在properties内部用property定义属性 -->
        <!-- 如果外部配置文件有该属性,则内部定义属性被外部属性覆盖 -->
        <property name="jdbc.username" value="root123" />
        <property name="jdbc.password" value="root123" />
    </properties>

    <!-- 和spring整合后 environments配置将废除 -->
    <environments default="development">
        <environment id="development">
            <!-- 使用jdbc事务管理 -->
            <transactionManager type="JDBC" />
            <!-- 数据库连接池 -->
            <dataSource type="POOLED">
                <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>

    <!-- 加载映射文件 -->
    <mappers>
        <mapper resource="sqlmap/User.xml" />
        <mapper resource="mapper/UserMapper.xml" />
    </mappers>
</configuration>

注意:MyBatis 将按照下面的顺序来加载属性:

  • 在properties 元素体内定义的属性首先被读取。
  • 然后会读取properties 元素中resource或 url 加载的属性,它会覆盖已读取的同名属性。

typeAliases(类型别名)

mybatis支持别名:
别名 映射的类型
_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
map Map

自定义别名:

在SqlMapConfig.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">
<configuration>
    <!-- 是用resource属性加载外部配置文件 -->
    <properties resource="db.properties">
        <!-- 在properties内部用property定义属性 -->
        <property name="jdbc.username" value="root123" />
        <property name="jdbc.password" value="root123" />
    </properties>

    <typeAliases>
        <!-- 单个别名定义 -->
        <typeAlias alias="user" type="com.igeek.pojo.User" />
        <!-- 批量别名定义,扫描整个包下的类,别名为类名(大小写不敏感) -->
        <package name="com.igeek.pojo" />
        <package name="其它包" />
    </typeAliases>

    <!-- 和spring整合后 environments配置将废除 -->
    <environments default="development">
        <environment id="development">
            <!-- 使用jdbc事务管理 -->
            <transactionManager type="JDBC" />
            <!-- 数据库连接池 -->
            <dataSource type="POOLED">
                <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>

    <!-- 加载映射文件 -->
    <mappers>
        <mapper resource="sqlmap/User.xml" />
        <mapper resource="mapper/UserMapper.xml" />
    </mappers>
</configuration>

在mapper.xml配置文件中,就可以使用设置的别名了
别名大小写不敏感
image.png

mappers(映射器)

Mapper配置的几种方法:

使用相对于类路径的资源(现在的使用方式)
如:

使用mapper接口类路径
如:
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。

注册指定包下的所有mapper接口
如:
注意:此种方法要求mapper接口名称和mapper映射文件名称相同,且放在同一个目录中。

输入映射和输出映射

Mapper.xml映射文件中定义了操作数据库的sql,每个sql是一个statement,映射文件是mybatis的核心。

环境准备

  1. 复制昨天的工程,如下图

image.png

  1. 只保留Mapper接口开发相关的文件,其他的删除

最终效果如下图:
image.png

  1. 如下图修改SqlMapConfig.xml配置文件。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>
     <!-- 是用resource属性加载外部配置文件 -->
     <properties resource="db.properties"></properties>
     <typeAliases>
         <package name="com.igeek" />
     </typeAliases>
    
     <!-- 和spring整合后 environments配置将废除 -->
     <environments default="development">
         <environment id="development">
             <!-- 使用jdbc事务管理 -->
             <transactionManager type="JDBC" />
             <!-- 数据库连接池 -->
             <dataSource type="POOLED">
                 <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>
    
     <!-- 加载映射文件 -->
     <mappers>
         <!-- 使用包扫描的方式 -->
         <package name="com.igeek"/>
     </mappers>
    </configuration>
    

    parameterType(输入类型)

    传递简单类型

    参考第一天内容。
    使用#{}占位符,或者${}进行sql拼接。

    传递pojo对象

    参考第一天的内容。
    Mybatis使用ognl表达式解析对象字段的值,#{}或者${}括号中的值为pojo属性名称。

    传递pojo包装对象

    开发中可以使用pojo传递查询条件。
    查询条件可能是综合的查询条件,不仅包括用户查询条件还包括其它的查询条件(比如查询用户信息的时候,将用户购买商品信息也作为查询条件),这时可以使用包装对象传递输入参数。
    包装对象:Pojo类中的一个属性是另外一个pojo。

需求:根据用户名模糊查询用户信息,查询条件放到QueryVo的user属性中。

编写QueryVo
public class QueryVo {
      // 包含其他的pojo
    private User user;

    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
}

Sql语句

SELECT * FROM user WHERE username LIKE ‘%张%’

Mapper.xml文件

在UserMapper.xml中配置sql,如下图。

<!-- 使用包装类型查询用户 -->
<select id="queryUserByQueryVo" parameterType="queryVo" resultType="user">
    SELECT * FROM `user` WHERE username LIKE '%${user.username}%'
</select>

Mapper接口

在UserMapper接口中添加方法,如下图:

/**
  * 
  * @Title: queryUserByQueryVo  
  * @Description: TODO(根据包装类查询用户)  
  * @param queryVo
  * @return
  */
List<User> queryUserByQueryVo(QueryVo queryVo);

测试方法

在UserMapeprTest增加测试方法,如下:

@Test
public void testQueryUserByQueryVo() {
    // mybatis和spring整合,整合之后,交给spring管理
    SqlSession sqlSession = this.sqlSessionFactory.openSession();
    // 创建Mapper接口的动态代理对象,整合之后,交给spring管理
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    // 使用userMapper执行查询,使用包装对象
    QueryVo queryVo = new QueryVo();
    // 设置user条件
    User user = new User();
    user.setUsername("张");
    // 设置到包装对象中
    queryVo.setUser(user);

    // 执行查询
    List<User> list = userMapper.queryUserByQueryVo(queryVo);
    for (User u : list) {
        System.out.println(u);
    }

    // mybatis和spring整合,整合之后,交给spring管理
    sqlSession.close();
}

效果

测试结果如下图:
image.png

resultType(输出类型)

输出简单类型

需求:查询用户表数据条数
sql:SELECT count(*) FROM user

Mapper.xml文件

在UserMapper.xml中配置sql:

<!-- 查询用户表数据条数 -->
<select id="queryUserCount" resultType="int">
    SELECT count(*) FROM `user`
</select>

Mapper接口

在UserMapper添加方法,:

/**
  * 
  * @Title: queryUserCount  
  * @Description: TODO(这里用一句话描述这个方法的作用)  
  * @return
  */
int queryUserCount();

测试方法

在UserMapeprTest增加测试方法,如下:

@Test
public void testQueryUserCount() {
    // mybatis和spring整合,整合之后,交给spring管理
    SqlSession sqlSession = this.sqlSessionFactory.openSession();
    // 创建Mapper接口的动态代理对象,整合之后,交给spring管理
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    // 使用userMapper执行查询用户数据条数
    int count = userMapper.queryUserCount();
    System.out.println(count);

    // mybatis和spring整合,整合之后,交给spring管理
    sqlSession.close();
}

效果

测试结果如下图:
image.png
注意:输出简单类型必须查询出来的结果集有一条记录,最终将第一个字段的值转换为输出类型。

输出pojo对象

参考第一天内容

输出pojo列表

参考第一天内容

resultMap

resultType可以指定将查询结果映射为pojo,但需要pojo的属性名和sql查询的列名一致方可映射成功。
如果sql查询字段名和pojo的属性名不一致,可以通过resultMap将字段名和属性名作一个对应关系 ,resultMap实质上还需要将查询结果映射到pojo对象中。
resultMap可以实现将查询结果映射为复杂类型的pojo,比如在查询结果映射对象中包括pojo和list实现一对一查询和一对多查询。

需求:查询订单表order的所有数据
sql:SELECT id, user_id, number, createtime, note FROM order

声明pojo对象

数据库表如下图:
image.png
Order对象:

public class Order {
    // 订单id
    private int id;
    // 用户id
    private Integer userId;
    // 订单号
    private String number;
    // 订单创建时间
    private Date createtime;
    // 备注
    private String note;
//get/set。。。
}

Mapper.xml文件

创建OrderMapper.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">
<!-- namespace:命名空间,用于隔离sql,还有一个很重要的作用,Mapper动态代理开发的时候使用,需要指定Mapper的类路径 -->
<mapper namespace="com.igeek.mapper.OrderMapper">
    <!-- 查询所有的订单数据 -->
    <select id="queryOrderAll" resultType="order">
        SELECT id, user_id,
        number,
        createtime, note FROM `orders`
    </select>
</mapper>

Mapper接口

编写接口如下:

public interface OrderMapper {
    /**
     * 查询所有订单
     * 
     * @return
     */
    List<Order> queryOrderAll();
}

测试方法

编写测试方法OrderMapperTest如下:

public class OrderMapperTest {
    private SqlSessionFactory sqlSessionFactory;

    @Before
    public void init() throws Exception {
        InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        this.sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }

    @Test
    public void testQueryAll() {
        // 获取sqlSession
        SqlSession sqlSession = this.sqlSessionFactory.openSession();
        // 获取OrderMapper
        OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class);

        // 执行查询
        List<Order> list = orderMapper.queryOrderAll();
        for (Order order : list) {
            System.out.println(order);
        }
    }
}

效果

测试效果如下图:
image.png
发现userId为null
解决方案:使用resultMap

使用resultMap

由于上边的mapper.xml中sql查询列(user_id)和Order类属性(userId)不一致,所以查询结果不能映射到pojo中。
需要定义resultMap,把orderResultMap将sql查询列(user_id)和Order类属性(userId)对应起来
改造OrderMapper.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">
<!-- namespace:命名空间,用于隔离sql,还有一个很重要的作用,Mapper动态代理开发的时候使用,需要指定Mapper的类路径 -->
<mapper namespace="com.igeek.mapper.OrderMapper">

    <!-- resultMap最终还是要将结果映射到pojo上,type就是指定映射到哪一个pojo -->
    <!-- id:设置ResultMap的id -->
    <resultMap type="order" id="orderResultMap">
        <!-- 定义主键 ,非常重要。如果是多个字段,则定义多个id -->
        <!-- property:主键在pojo中的属性名 -->
        <!-- column:主键在数据库中的列名 -->
        <id property="id" column="id" />

        <!-- 定义普通属性 -->
        <result property="userId" column="user_id" />
        <result property="number" column="number" />
        <result property="createtime" column="createtime" />
        <result property="note" column="note" />
    </resultMap>

    <!-- 查询所有的订单数据 -->
    <select id="queryOrderAll" resultMap="orderResultMap">
        SELECT id, user_id,
        number,
        createtime, note FROM `orders`
    </select>

</mapper>

效果

只需要修改Mapper.xml就可以了,再次测试结果如下:
image.png

动态sql

通过mybatis提供的各种标签方法实现动态拼接sql。
需求:根据性别和名字查询用户
查询sql:
SELECT id, username, birthday, sex, address FROM user WHERE sex = 1 AND username LIKE ‘%张%’

If标签

Mapper.xml文件

UserMapper.xml配置sql,如下:

<!-- 根据条件查询用户 -->
<select id="queryUserByWhere" parameterType="user" resultType="user">
    SELECT id, username, birthday, sex, address FROM `user`
    WHERE sex = #{sex} AND username LIKE
    '%${username}%'
</select>

Mapper接口

编写Mapper接口:

/**
  * 
  * @Title: queryUserByWhere  
  * @Description: 根据条件查询用户
  * @param user
  * @return 
  */
List<User> queryUserByWhere(User user);

测试方法

在UserMapperTest添加测试方法,如下:

@Test
public void testQueryUserByWhere() {
    // mybatis和spring整合,整合之后,交给spring管理
    SqlSession sqlSession = this.sqlSessionFactory.openSession();
    // 创建Mapper接口的动态代理对象,整合之后,交给spring管理
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    // 使用userMapper执行根据条件查询用户
    User user = new User();
    user.setSex("1");
    user.setUsername("张");

    List<User> list = userMapper.queryUserByWhere(user);

    for (User u : list) {
        System.out.println(u);
    }

    // mybatis和spring整合,整合之后,交给spring管理
    sqlSession.close();
}

效果

测试效果如下图:
image.png
如果注释掉 user.setSex(“1”),测试结果如下图:
image.png
测试结果二很显然不合理。
按照之前所学的,要解决这个问题,需要编写多个sql,查询条件越多,需要编写的sql就更多了,显然这样是不靠谱的。

解决方案,使用动态sql的if标签

使用if标签

改造UserMapper.xml,如下:

<!-- 根据条件查询用户 -->
<select id="queryUserByWhere" parameterType="user" resultType="user">
    SELECT id, username, birthday, sex, address FROM `user`
    WHERE 1=1
    <if test="sex != null and sex != ''">
        AND sex = #{sex}
    </if>
    <if test="username != null and username != ''">
        AND username LIKE
        '%${username}%'
    </if>
</select>

注意字符串类型的数据需要要做不等于空字符串校验。

效果

image.png
如上图所示,测试OK

Where标签

上面的sql还有where 1=1 这样的语句,很麻烦
可以使用where标签进行改造

改造UserMapper.xml,如下

<!-- 根据条件查询用户 -->
<select id="queryUserByWhere" parameterType="user" resultType="user">
    SELECT id, username, birthday, sex, address FROM `user`
<!-- where标签可以自动添加where,同时处理sql语句中第一个and关键字 -->
    <where>
        <if test="sex != null">
            AND sex = #{sex}
        </if>
        <if test="username != null and username != ''">
            AND username LIKE
            '%${username}%'
        </if>
    </where>
</select>

效果

测试效果如下图:
image.png

Sql片段

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

把上面例子中的id, username, birthday, sex, address提取出来,作为sql片段,如下:

<!-- 声明sql片段 -->
<sql id="userFields">
    id, username, birthday, sex, address
</sql>

<!-- 根据条件查询用户 -->
<select id="queryUserByWhere" parameterType="user" resultType="user">
    <!-- SELECT id, username, birthday, sex, address FROM `user` -->
    <!-- 使用include标签加载sql片段;refid是sql片段id -->
    SELECT <include refid="userFields" /> FROM `user`
    <!-- where标签可以自动添加where关键字,同时处理sql语句中第一个and关键字 -->
    <where>
        <if test="sex != null">
            AND sex = #{sex}
        </if>
        <if test="username != null and username != ''">
            AND username LIKE
            '%${username}%'
        </if>
    </where>
</select>

如果要使用别的Mapper.xml配置的sql片段,可以在refid前面加上对应的Mapper.xml的namespace
例如下图
image.png

foreach标签

向sql传递数组或List,mybatis使用foreach解析,如下:

根据多个id查询用户信息
查询sql:
SELECT * FROM user WHERE id IN (1,10,24)

改造QueryVo

如下代码,在pojo中定义list属性ids存储多个用户id,并添加getter/setter方法

public class QueryVo {
    // 包含其他的pojo
    private User user;

    private List<Integer> ids;

    public List<Integer> getIds() {
        return ids;
    }
    public void setIds(List<Integer> ids) {
        this.ids = ids;
    }
    public User getUser() {
        return user;
    }
    public void setUser(User user) {
        this.user = user;
    }
}

Mapper.xml文件

UserMapper.xml添加sql,如下:

<!-- 根据ids查询用户 -->
<select id="queryUserByIds" parameterType="queryVo" resultType="user">
    SELECT * FROM `user`
    <where>
        <!-- foreach标签,进行遍历 -->
        <!-- collection:遍历的集合,这里是QueryVo的ids属性 -->
        <!-- item:遍历的项目,可以随便写,,但是和后面的#{}里面要一致 -->
        <!-- open:在前面添加的sql片段 -->
        <!-- close:在结尾处添加的sql片段 -->
        <!-- separator:指定遍历的元素之间使用的分隔符 -->
        <foreach collection="ids" item="item" open="id IN (" close=")" separator=",">
            #{item}
        </foreach>
    </where>
</select>

在UserMapper接口中添加如下方法

List<User>  queryUserByIds(QueryVo queryVo);

测试方法如下图:

@Test
public void testQueryUserByIds() {
    // mybatis和spring整合,整合之后,交给spring管理
    SqlSession sqlSession = this.sqlSessionFactory.openSession();
    // 创建Mapper接口的动态代理对象,整合之后,交给spring管理
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    // 使用userMapper执行根据条件查询用户
    QueryVo queryVo = new QueryVo();
    List<Integer> ids = new ArrayList<>();
    ids.add(1);
    ids.add(10);
    ids.add(24);
    queryVo.setIds(ids);

    List<User> list = userMapper.queryUserByIds(queryVo);

    for (User u : list) {
        System.out.println(u);
    }

    // mybatis和spring整合,整合之后,交给spring管理
    sqlSession.close();
}

效果

测试效果如下图:
image.png

关联查询

商品订单数据模型

image.png

一对一查询

需求:查询所有订单信息,关联查询下单用户信息。

注意:因为一个订单信息只会是一个人下的订单,所以从查询订单信息出发关联查询用户信息为一对一查询。如果从用户信息出发查询用户下的订单信息则为一对多查询,因为一个用户可以下多个订单。

sql语句:

SELECT
    o.id,
    o.user_id userId,
    o.number,
    o.createtime,
    o.note,
    u.username,
    u.address
FROM
    `orders` o
LEFT JOIN `user` u ON o.user_id = u.id

方法一:使用resultType

使用resultType,改造订单pojo类,此pojo类中包括了订单信息和用户信息
这样返回对象的时候,mybatis自动把用户信息也注入进来了

改造pojo类

OrderUser类继承Order类后OrderUser类包括了Order类的所有字段,只需要定义用户的信息字段即可,如下:

public class OrderUser extends Order{
    private String username;
    private String address;
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getAddress() {
        return address;
    }
    public void setAddress(String address) {
        this.address = address;
    }
}

Mapper.xml

在UserMapper.xml添加sql,如下

<!-- 查询订单,同时包含用户数据 -->
<select id="queryOrderUser" resultType="orderUser">
    SELECT
    o.id,
    o.user_id
    userId,
    o.number,
    o.createtime,
    o.note,
    u.username,
    u.address
    FROM
    `orders` o
    LEFT JOIN `user` u ON o.user_id = u.id
</select>

Mapper接口

在UserMapper接口添加方法,如下代码:

/**
  * 
  * @Title: queryOrderUser  
  * @Description: TODO(一对一关联,查询订单同时包含用户信息)  
  * @return
  */
List<OrderUser> queryOrderUser();

测试方法:

在UserMapperTest添加测试方法,如下:

@Test
public void testQueryOrderUser() {
    // mybatis和spring整合,整合之后,交给spring管理
    SqlSession sqlSession = this.sqlSessionFactory.openSession();
    // 创建Mapper接口的动态代理对象,整合之后,交给spring管理
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    // 使用userMapper执行根据条件查询用户
    List<OrderUser> list = userMapper.queryOrderUser();

    for (OrderUser ou : list) {
        System.out.println(ou);
    }

    // mybatis和spring整合,整合之后,交给spring管理
    sqlSession.close();
}

效果

测试结果如下图:
image.png

小结

定义专门的pojo类作为输出类型,其中定义了sql查询结果集所有的字段。此方法较为简单,企业中使用普遍。

方法二:使用resultMap

使用resultMap,定义专门的resultMap用于映射一对一查询结果。

改造pojo类

在Order类中加入User属性,user属性中用于存储关联查询的用户信息,因为订单关联查询用户是一对一关系,所以这里使用单个User对象存储关联查询的用户信息。
改造Order如下代码:

public class Order {
    // 订单id
    private int id;
    // 用户id
    private Integer userId;
    // 订单号
    private String number;
    // 订单创建时间
    private Date createtime;
    // 备注
    private String note;

    private User user;
//get/set。。。
}

Mapper.xml

这里resultMap指定orderUserResultMap,如下:

<resultMap type="order" id="orderUserResultMap">
    <id property="id" column="id" />
    <result property="userId" column="user_id" />
    <result property="number" column="number" />
    <result property="createtime" column="createtime" />
    <result property="note" column="note" />

    <!-- association :配置一对一属性 -->
    <!-- property:Order里面的User属性名 -->
    <!-- javaType:属性类型 -->
    <association property="user" javaType="user">
        <!-- id:声明主键,表示user_id是关联查询对象的唯一标识-->
        <id property="id" column="user_id" />
        <result property="username" column="username" />
        <result property="address" column="address" />
    </association>

</resultMap>

<!-- 一对一关联,查询订单,订单内部包含用户属性 -->
<select id="queryOrderUserResultMap" resultMap="orderUserResultMap">
    SELECT
    o.id,
    o.user_id,
    o.number,
    o.createtime,
    o.note,
    u.username,
    u.address
    FROM
    `orders` o
    LEFT JOIN `user` u ON o.user_id = u.id
</select>

Mapper接口

编写UserMapper如下代码:

/**
  * 
  * @Title: queryOrderUserResultMap  
  * @Description: TODO(一对一关联,查询订单,订单内部包含用户属性    )  
  * @return
  */
List<Order> queryOrderUserResultMap();

测试方法

在UserMapperTest增加测试方法,如下:

@Test
public void testQueryOrderUserResultMap() {
    // mybatis和spring整合,整合之后,交给spring管理
    SqlSession sqlSession = this.sqlSessionFactory.openSession();
    // 创建Mapper接口的动态代理对象,整合之后,交给spring管理
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    // 使用userMapper执行根据条件查询用户
    List<Order> list = userMapper.queryOrderUserResultMap();

    for (Order o : list) {
        System.out.println(o);
    }

    // mybatis和spring整合,整合之后,交给spring管理
    sqlSession.close();
}

效果

测试效果如下图:
image.png

一对多查询

案例:查询所有用户信息及用户关联的订单信息。
用户信息和订单信息为一对多关系。

sql语句:

SELECT
    u.id,
    u.username,
    u.birthday,
    u.sex,
    u.address,
    o.id oid,
    o.number,
    o.createtime,
    o.note
FROM
    `user` u
LEFT JOIN `orders` o ON u.id = o.user_id

修改pojo类

在User类中加入List orders属性,如下图:
image.png

Mapper.xml

在UserMapper.xml添加sql,如下:

<resultMap type="user" id="userOrderResultMap">
    <id property="id" column="id" />
    <result property="username" column="username" />
    <result property="birthday" column="birthday" />
    <result property="sex" column="sex" />
    <result property="address" column="address" />

    <!-- 配置一对多的关系 -->
    <collection property="orders" javaType="list" ofType="order">
        <!-- 配置主键,是关联Order的唯一标识 -->
        <id property="id" column="oid" />
        <result property="number" column="number" />
        <result property="createtime" column="createtime" />
        <result property="note" column="note" />
    </collection>
</resultMap>

<!-- 一对多关联,查询订单同时查询该用户下的订单 -->
<select id="queryUserOrder" resultMap="userOrderResultMap">
    SELECT
    u.id,
    u.username,
    u.birthday,
    u.sex,
    u.address,
    o.id oid,
    o.number,
    o.createtime,
    o.note
    FROM
    `user` u
    LEFT JOIN `orders` o ON u.id = o.user_id
</select>

Mapper接口

编写UserMapper接口,如下图:
image.png

测试方法

在UserMapperTest增加测试方法,如下

@Test
public void testQueryUserOrder() {
    // mybatis和spring整合,整合之后,交给spring管理
    SqlSession sqlSession = this.sqlSessionFactory.openSession();
    // 创建Mapper接口的动态代理对象,整合之后,交给spring管理
    UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

    // 使用userMapper执行根据条件查询用户
    List<User> list = userMapper.queryUserOrder();

    for (User u : list) {
        System.out.println(u);
    }

    // mybatis和spring整合,整合之后,交给spring管理
    sqlSession.close();
}

效果

测试效果如下图:
image.png

Mybatis整合spring

整合思路

  1. SqlSessionFactory对象应该放到spring容器中作为单例存在。
  2. 传统dao的开发方式中,应该从spring容器中获得sqlsession对象。
  3. Mapper代理形式中,应该从spring容器中直接获得mapper的代理对象。
  4. 数据库的连接以及数据库连接池事务管理都交给spring容器来完成。

    整合需要的jar包

    1、spring的jar包
    2、Mybatis的jar包
    3、Spring+mybatis的整合包。
    4、Mysql的数据库驱动jar包。
    5、数据库连接池的jar包。
    jar包位置如下所示:
    image.png

    整合的步骤

    创建工程

    如下图创建一个web工程:
    image.png

    导入jar包

    前面提到的jar包需要导入,如下图:
    image.png

    加入配置文件

  5. mybatis的配置文件 sqlmapConfig.xml

  6. Spring的配置文件 applicationContext.xml
    1. 数据库连接及连接池
    2. 事务管理(暂时可以不配置)
    3. sqlsessionFactory对象,配置到spring容器中
    4. mapeer代理对象或者是dao实现类配置到spring容器中

创建资源文件夹config拷贝加入配置文件,如下图
image.png

SqlMapConfig.xml

配置文件是SqlMapConfig.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">
<configuration>
    <!-- 设置别名 -->
    <typeAliases>
        <!-- 2. 指定扫描包,会把包内所有的类都设置别名,别名的名称就是类名,大小写不敏感 -->
        <package name="com.igeek.pojo" />
    </typeAliases>

</configuration>

applicationContext.xml

SqlSessionFactoryBean属于mybatis-spring这个jar包
对于spring来说,mybatis是另外一个架构,需要整合jar包。

在项目中加入mybatis-spring-1.2.2.jar的源码,如下图
image.png
image.png
效果,如下图所示,图标变化,表示源码加载成功:
image.png
整合Mybatis需要的是SqlSessionFactoryBean,位置如下图:
image.png
applicationContext.xml,配置内容如下

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
    http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
    http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-4.0.xsd">

   <!-- 加载配置文件 -->
   <context:property-placeholder location="classpath:db.properties" />

    <!-- 数据库连接池 -->
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
        destroy-method="close">
        <property name="driverClassName" value="${jdbc.driver}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="maxActive" value="10" />
        <property name="maxIdle" value="5" />
    </bean>

    <!-- 配置SqlSessionFactory -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!-- 配置mybatis核心配置文件 -->
        <property name="configLocation" value="classpath:SqlMapConfig.xml" />
        <!-- 配置数据源 -->
        <property name="dataSource" ref="dataSource" />
    </bean>
</beans>

db.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8
jdbc.username=root
jdbc.password=root

log4j2配置文件

使用log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
    <Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
        </Console>
    </Appenders>

    <Loggers>
        <Root level="error" >
            <AppenderRef ref="Console" />
        </Root>

        <logger name="com.igeek.servlet_bili.mapper" level="debug" additivity="false">
            <AppenderRef ref="Console" />
        </logger>
    </Loggers>

</Configuration>

或者log4j.properties

# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

效果:

加入的配置文件最终效果如下:
image.png

Dao的开发

两种dao的实现方式:

  1. 原始dao的开发方式
  2. 使用Mapper代理形式开发方式
    1. 直接配置Mapper代理
    2. 使用扫描包配置Mapper代理

需求:
1. 实现根据用户id查询
2. 实现根据用户名模糊查询
3. 添加用户

创建pojo

public class User {
    private int id;
    private String username;// 用户姓名
    private String sex;// 性别
    private Date birthday;// 生日
    private String address;// 地址

//get/set。。。
}

传统dao的开发方式

原始的DAO开发接口+实现类来完成。
需要dao实现类需要继承SqlsessionDaoSupport类

实现Mapper.xml

编写User.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="test">
    <!-- 根据用户id查询 -->
    <select id="queryUserById" parameterType="int" resultType="user">
        select * from user where id = #{id}
    </select>

    <!-- 根据用户名模糊查询用户 -->
    <select id="queryUserByUsername" parameterType="string"
        resultType="user">
        select * from user where username like '%${value}%'
    </select>

    <!-- 添加用户 -->
    <insert id="saveUser" parameterType="user">
        <selectKey keyProperty="id" keyColumn="id" order="AFTER"
            resultType="int">
            select last_insert_id()
        </selectKey>
        insert into user
        (username,birthday,sex,address)
        values
        (#{username},#{birthday},#{sex},#{address})
    </insert>

</mapper>

加载Mapper.xml

在SqlMapConfig如下图进行配置:
image.png

实现UserDao接口
public interface UserDao {
    /**
     * 根据id查询用户
     * 
     * @param id
     * @return
     */
    User queryUserById(int id);

    /**
     * 根据用户名模糊查询用户列表
     * 
     * @param username
     * @return
     */
    List<User> queryUserByUsername(String username);

    /**
     * 保存
     * 
     * @param user
     */
    void saveUser(User user);

}

实现UserDaoImpl实现类

编写DAO实现类,实现类必须集成SqlSessionDaoSupport
SqlSessionDaoSupport提供getSqlSession()方法来获取SqlSession

public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {
    @Override
    public User queryUserById(int id) {
        // 获取SqlSession
        SqlSession sqlSession = super.getSqlSession();

        // 使用SqlSession执行操作
        User user = sqlSession.selectOne("queryUserById", id);

        // 不要关闭sqlSession

        return user;
    }

    @Override
    public List<User> queryUserByUsername(String username) {
        // 获取SqlSession
        SqlSession sqlSession = super.getSqlSession();

        // 使用SqlSession执行操作
        List<User> list = sqlSession.selectList("queryUserByUsername", username);

        // 不要关闭sqlSession

        return list;
    }

    @Override
    public void saveUser(User user) {
        // 获取SqlSession
        SqlSession sqlSession = super.getSqlSession();

        // 使用SqlSession执行操作
        sqlSession.insert("saveUser", user);

        // 不用提交,事务由spring进行管理
        // 不要关闭sqlSession
    }
}

配置dao

把dao实现类配置到spring容器中,如下图

<!-- 原始方法开始dao,配置dao到spring中 -->
<bean id="userDao" class="com.igeek.dao.UserDaoImpl">
        <!--配置sqlSessionFactory -->
        <property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
</bean>

测试方法

创建测试方法,可以直接创建测试Junit用例。
创建包:com.igeek.test

编写测试方法如下:

public class UserDaoTest {
    private ApplicationContext context;

    @Before
    public void setUp() throws Exception {
        this.context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    }

    @Test
    public void testQueryUserById() {
        // 获取userDao
        UserDao userDao = this.context.getBean(UserDao.class);

        User user = userDao.queryUserById(1);
        System.out.println(user);
    }

    @Test
    public void testQueryUserByUsername() {
        // 获取userDao
        UserDao userDao = this.context.getBean(UserDao.class);

        List<User> list = userDao.queryUserByUsername("张");
        for (User user : list) {
            System.out.println(user);
        }
    }

    @Test
    public void testSaveUser() {
        // 获取userDao
        UserDao userDao = this.context.getBean(UserDao.class);

        User user = new User();
        user.setUsername("曹操");
        user.setSex("1");
        user.setBirthday(new Date());
        user.setAddress("三国");
        userDao.saveUser(user);
        System.out.println(user);
    }
}

Mapper代理形式开发dao

实现Mapper.xml

编写UserMapper.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.igeek.mapper.UserMapper">
    <!-- 根据用户id查询 -->
    <select id="queryUserById" parameterType="int" resultType="user">
        select * from user where id = #{id}
    </select>

    <!-- 根据用户名模糊查询用户 -->
    <select id="queryUserByUsername" parameterType="string"
        resultType="user">
        select * from user where username like '%${value}%'
    </select>

    <!-- 添加用户 -->
    <insert id="saveUser" parameterType="user">
        <selectKey keyProperty="id" keyColumn="id" order="AFTER"
            resultType="int">
            select last_insert_id()
        </selectKey>
        insert into user
        (username,birthday,sex,address) values
        (#{username},#{birthday},#{sex},#{address})
    </insert>
</mapper>

实现UserMapper接口
public interface UserMapper {
    /**
     * 根据用户id查询
     * 
     * @param id
     * @return
     */
    User queryUserById(int id);

    /**
     * 根据用户名模糊查询用户
     * 
     * @param username
     * @return
     */
    List<User> queryUserByUsername(String username);

    /**
     * 添加用户
     * 
     * @param user
     */
    void saveUser(User user);
}

方式一:配置mapper代理

在applicationContext.xml添加配置
MapperFactoryBean也是属于mybatis-spring整合包

<!-- Mapper代理的方式开发方式一,配置Mapper代理对象 -->
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <!-- 配置Mapper接口 -->
    <property name="mapperInterface" value="com.igeek.mapper.UserMapper" />
    <!-- 配置sqlSessionFactory -->
    <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

测试方法
public class UserMapperTest {
    private ApplicationContext context;

    @Before
    public void setUp() throws Exception {
        this.context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    }

    @Test
    public void testQueryUserById() {
        // 获取Mapper
        UserMapper userMapper = this.context.getBean(UserMapper.class);

        User user = userMapper.queryUserById(1);
        System.out.println(user);
    }

    @Test
    public void testQueryUserByUsername() {
        // 获取Mapper
        UserMapper userMapper = this.context.getBean(UserMapper.class);

        List<User> list = userMapper.queryUserByUsername("张");

        for (User user : list) {
            System.out.println(user);
        }
    }
    @Test
    public void testSaveUser() {
        // 获取Mapper
        UserMapper userMapper = this.context.getBean(UserMapper.class);

        User user = new User();
        user.setUsername("曹操");
        user.setSex("1");
        user.setBirthday(new Date());
        user.setAddress("三国");

        userMapper.saveUser(user);
        System.out.println(user);
    }
}

方式二:扫描包形式配置mapper
<!-- Mapper代理的方式开发方式二,扫描包方式配置代理 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <!-- 配置Mapper接口 -->
    <property name="basePackage" value="com.igeek.mapper" />
</bean>

每个mapper代理对象的id就是类名,首字母小写

Mybatis逆向工程

使用官方网站的Mapper自动生成工具mybatis-generator-core-1.3.2来生成po类和Mapper映射文件

导入逆向工程

使用课前资料已有逆向工程,如下图:
image.png

导入逆向工程到eclipse中

如下图方式进行导入:
image.png
image.png

修改配置文件

在generatorConfig.xml中配置Mapper生成的详细信息,如下图:
image.png
注意修改以下几点:
1. 修改要生成的数据库表
2. pojo文件所在包路径
3. Mapper所在的包路径

配置文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
  PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
  "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <context id="testTables" targetRuntime="MyBatis3">
        <commentGenerator>
            <!-- 是否去除自动生成的注释 true:是 : false:否 -->
            <property name="suppressAllComments" value="true" />
        </commentGenerator>
        <!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
            connectionURL="jdbc:mysql://localhost:3306/mybatis" userId="root" password="root">
        </jdbcConnection>
        <!-- <jdbcConnection driverClass="oracle.jdbc.OracleDriver" connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:yycg" 
            userId="yycg" password="yycg"> </jdbcConnection> -->

        <!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 
            和 NUMERIC 类型解析为java.math.BigDecimal -->
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false" />
        </javaTypeResolver>

        <!-- targetProject:生成PO类的位置 -->
        <javaModelGenerator targetPackage="com.igeek.ssm.po"
            targetProject=".\src">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false" />
            <!-- 从数据库返回的值被清理前后的空格 -->
            <property name="trimStrings" value="true" />
        </javaModelGenerator>
        <!-- targetProject:mapper映射文件生成的位置 -->
        <sqlMapGenerator targetPackage="com.igeek.ssm.mapper"
            targetProject=".\src">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false" />
        </sqlMapGenerator>
        <!-- targetPackage:mapper接口生成的位置 -->
        <javaClientGenerator type="XMLMAPPER"
            targetPackage="com.igeek.ssm.mapper" targetProject=".\src">
            <!-- enableSubPackages:是否让schema作为包的后缀 -->
            <property name="enableSubPackages" value="false" />
        </javaClientGenerator>
        <!-- 指定数据库表 -->
        <table schema="" tableName="user"></table>
        <table schema="" tableName="orders"></table>
    </context>
</generatorConfiguration>

生成逆向工程代码

找到下图所示的java文件,执行工程main主函数,
image.png
image.png
刷新工程,发现代码生成,如下图:
image.png

测试逆向工程代码

  1. 复制生成的代码到mybatis_d02_c05工程,如下图

image.png

  1. 修改spring配置文件

在applicationContext.xml修改

<!-- Mapper代理的方式开发,扫描包方式配置代理 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <!-- 配置Mapper接口,如果需要加载多个包,直接写进来,中间用,分隔 -->
    <!-- <property name="basePackage" value="com.igeek.mapper" /> -->
    <property name="basePackage" value="com.igeek.ssm.mapper" />
</bean>
  1. 编写测试方法:

    public class UserMapperTest {
     private ApplicationContext context;
    
     @Before
     public void setUp() throws Exception {
         this.context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
     }
    
     @Test
     public void testInsert() {
         // 获取Mapper
         UserMapper userMapper = this.context.getBean(UserMapper.class);
    
         User user = new User();
         user.setUsername("曹操");
         user.setSex("1");
         user.setBirthday(new Date());
         user.setAddress("三国");
    
         userMapper.insert(user);
     }
    
     @Test
     public void testSelectByExample() {
         // 获取Mapper
         UserMapper userMapper = this.context.getBean(UserMapper.class);
    
         // 创建User对象扩展类,用户设置查询条件
         UserExample example = new UserExample();
         example.createCriteria().andUsernameLike("%张%");
    
         // 查询数据
         List<User> list = userMapper.selectByExample(example);
    
         System.out.println(list.size());
     }
    
     @Test
     public void testSelectByPrimaryKey() {
         // 获取Mapper
         UserMapper userMapper = this.context.getBean(UserMapper.class);
    
         User user = userMapper.selectByPrimaryKey(1);
         System.out.println(user);
     }
    }
    

    注意:
    1. 逆向工程生成的代码只能做单表查询
    2. 不能在生成的代码上进行扩展,因为如果数据库变更,需要重新使用逆向工程生成代码,原来编写的代码就被覆盖了。
    3. 一张表会生成4个文件