02.MyBatis的操作

一、MyBatis的CRUD操作

案例:用户信息表的操作

我们使用面向接口编程来实现。<mapper namespace="com.sdbi.mapper.UserMapper">

(一)添加

添加一条用户信息。

1、在UserMapper.java接口中定义添加方法

package com.sdbi.mapper;

import com.sdbi.pojo.User;

public interface UserMapper {
    User findByName(String name);

    User findById(Integer id);

    int insertUser(User user);
}

2、在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为映射根节点 -->
<mapper namespace="com.sdbi.mapper.UserMapper">
    <!-- id是接口中的“方法名”,parameterType是传入参数的数据类型,resultType是返回的实体类名(包名.类名)-->
    <select id="findByName" parameterType="String" resultType="com.sdbi.pojo.User">
        select *
        from tb_users
        where name = #{name}
    </select>

    <select id="findById" parameterType="Integer" resultType="com.sdbi.pojo.User">
        select *
        from tb_users
        where id = #{id}
    </select>

    <insert id="insertUser">
        insert into tb_users (name, password, email, birthday)
        values (#{name}, #{password}, #{email}, #{birthday})
    </insert>
</mapper>

由于我们在UserMapper.java接口中已经为创建的insertUser()方法指定了参数是com.sdbi.pojo.User,所以在<insert>标签中我们可以省略parameterType属性,不必写出来。

3、调用添加方法,测试效果

(1)修改User类,增加两个构造方法

这里注意,如果定义了有参的构造方法,那么这里一定也要重写无参的构造方法,因为MyBatis框架会去调用这个无参的构造方法来构造对象,封装数据。

package com.sdbi.pojo;

public class User {
    private int id;
    private String name;
    private String password;
    private String email;
    private String birthday;

    // 如果定义了有参的构造方法,那么这里一定也要重写无参的构造方法,因为MyBatis框架会去调用这个无参的构造方法来构造对象,封装数据
    public User() {
    }

    public User(int id, String name, String password, String email, String birthday) {
        this.id = id;
        this.name = name;
        this.password = password;
        this.email = email;
        this.birthday = birthday;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getBirthday() {
        return birthday;
    }

    public void setBirthday(String birthday) {
        this.birthday = birthday;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                ", email='" + email + '\'' +
                ", birthday='" + birthday + '\'' +
                '}';
    }
}

(2)修改UserServlet类,定义添加数据的insertUser()方法,并且在doGet()方法中调用该方法。

另外,为了让代码结构清晰,我们将之前的查询功能代码也单独封装到findByName()方法中。

 1 package com.sdbi.servlet;
 2 
 3 import com.sdbi.mapper.UserMapper;
 4 import com.sdbi.pojo.User;
 5 import org.apache.ibatis.io.Resources;
 6 import org.apache.ibatis.session.SqlSession;
 7 import org.apache.ibatis.session.SqlSessionFactory;
 8 import org.apache.ibatis.session.SqlSessionFactoryBuilder;
 9 
10 import javax.servlet.ServletException;
11 import javax.servlet.annotation.WebServlet;
12 import javax.servlet.http.HttpServlet;
13 import javax.servlet.http.HttpServletRequest;
14 import javax.servlet.http.HttpServletResponse;
15 import java.io.IOException;
16 import java.io.Reader;
17 
18 @WebServlet("/UserServlet")
19 public class UserServlet extends HttpServlet {
20     @Override
21     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
22         System.out.println("doGet()...start");
23         resp.setContentType("text/html;charset=utf-8");
24         resp.getWriter().println("MyBatis:");
25 //        findByName(req, resp);
26         insertUser(req, resp);
27         System.out.println("doGet()...end");
28     }
29 
30     private void insertUser(HttpServletRequest req, HttpServletResponse resp) throws IOException {
31         System.out.println("insertUser()...start");
32         String resouces = "mybatis-config.xml";
33         // 创建流
34         Reader reader = null;
35         try {
36             // 读取mybatis-config.xml文件内容到reader对象中
37             reader = Resources.getResourceAsReader(resouces);
38             // 初始化MyBatis数据源,创建SqlSessionFactory类的实例
39             SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
40             // 创建SqlSession类的实例
41             SqlSession session = sqlSessionFactory.openSession();
42             // 调用SqlSession的getMapper方法,获取指定接口的实现类对象
43             UserMapper userMapper = session.getMapper(UserMapper.class);
44             int i = userMapper.insertUser(new User(0, "zhaoliu", "111", "zhaoliu@163.com", "1981-08-11"));
45             resp.getWriter().println("插入" + i + "条记录");
46             // 关闭SqlSession对象
47             session.close();
48         } catch (Exception e) {
49             e.printStackTrace();
50         }
51         System.out.println("insertUser()...end");
52     }
53 
54     private void findByName(HttpServletRequest req, HttpServletResponse resp) throws IOException {
55         System.out.println("findByName()...start");
56         String resouces = "mybatis-config.xml";
57         // 创建流
58         Reader reader = null;
59         try {
60             // 读取mybatis-config.xml文件内容到reader对象中
61             reader = Resources.getResourceAsReader(resouces);
62 
63             // 初始化MyBatis数据源,创建SqlSessionFactory类的实例
64             SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
65             // 创建SqlSession类的实例
66             SqlSession session = sqlSessionFactory.openSession();
67             // 调用SqlSession的getMapper方法,获取指定接口的实现类对象
68             UserMapper userMapper = session.getMapper(UserMapper.class);
69             // 传入参数,调用实现类对象的方法,返回结果User
70             User user = userMapper.findByName("zhangsan");
71 //        User user = userMapper.findById(3);
72             // 输出结果
73             if (user != null) {
74                 System.out.println(user.toString());
75                 resp.getWriter().println(user.toString());
76             } else {
77                 System.out.println("not found.");
78                 resp.getWriter().println("not found.");
79             }
80             // 关闭SqlSession对象
81             session.close();
82         } catch (Exception e) {
83             e.printStackTrace();
84         }
85         System.out.println("findByName()...end");
86     }
87 }

运行程序,结果如下:

虽然提示成功“插入1条记录”,但是我们查询数据库发现,tb_users表里没有并没有增加数据。

这是为什么呢?

这是因为,我们向数据库里添加了数据,但是没有提交这个事务。

我们在第46行的位置增加session.commit(); 

 1 package com.sdbi.servlet;
 2 
 3 import com.sdbi.mapper.UserMapper;
 4 import com.sdbi.pojo.User;
 5 import org.apache.ibatis.io.Resources;
 6 import org.apache.ibatis.session.SqlSession;
 7 import org.apache.ibatis.session.SqlSessionFactory;
 8 import org.apache.ibatis.session.SqlSessionFactoryBuilder;
 9 
10 import javax.servlet.ServletException;
11 import javax.servlet.annotation.WebServlet;
12 import javax.servlet.http.HttpServlet;
13 import javax.servlet.http.HttpServletRequest;
14 import javax.servlet.http.HttpServletResponse;
15 import java.io.IOException;
16 import java.io.Reader;
17 
18 @WebServlet("/UserServlet")
19 public class UserServlet extends HttpServlet {
20     @Override
21     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
22         System.out.println("doGet()...start");
23         resp.setContentType("text/html;charset=utf-8");
24         resp.getWriter().println("MyBatis:");
25 //        findByName(req, resp);
26         insertUser(req, resp);
27         System.out.println("doGet()...end");
28     }
29 
30     private void insertUser(HttpServletRequest req, HttpServletResponse resp) throws IOException {
31         System.out.println("insertUser()...start");
32         String resouces = "mybatis-config.xml";
33         // 创建流
34         Reader reader = null;
35         try {
36             // 读取mybatis-config.xml文件内容到reader对象中
37             reader = Resources.getResourceAsReader(resouces);
38             // 初始化MyBatis数据源,创建SqlSessionFactory类的实例
39             SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
40             // 创建SqlSession类的实例
41             SqlSession session = sqlSessionFactory.openSession();
42             // 调用SqlSession的getMapper方法,获取指定接口的实现类对象
43             UserMapper userMapper = session.getMapper(UserMapper.class);
44             int i = userMapper.insertUser(new User(0, "zhaoliu", "111", "zhaoliu@163.com", "1981-08-11"));
45             resp.getWriter().println("插入" + i + "条记录");
46             session.commit(); // 提交事务
47             // 关闭SqlSession对象
48             session.close();
49         } catch (Exception e) {
50             e.printStackTrace();
51         }
52         System.out.println("insertUser()...end");
53     }
54 
55     private void findByName(HttpServletRequest req, HttpServletResponse resp) throws IOException {
56         System.out.println("findByName()...start");
57         String resouces = "mybatis-config.xml";
58         // 创建流
59         Reader reader = null;
60         try {
61             // 读取mybatis-config.xml文件内容到reader对象中
62             reader = Resources.getResourceAsReader(resouces);
63 
64             // 初始化MyBatis数据源,创建SqlSessionFactory类的实例
65             SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
66             // 创建SqlSession类的实例
67             SqlSession session = sqlSessionFactory.openSession();
68             // 调用SqlSession的getMapper方法,获取指定接口的实现类对象
69             UserMapper userMapper = session.getMapper(UserMapper.class);
70             // 传入参数,调用实现类对象的方法,返回结果User
71             User user = userMapper.findByName("zhangsan");
72 //        User user = userMapper.findById(3);
73             // 输出结果
74             if (user != null) {
75                 System.out.println(user.toString());
76                 resp.getWriter().println(user.toString());
77             } else {
78                 System.out.println("not found.");
79                 resp.getWriter().println("not found.");
80             }
81             // 关闭SqlSession对象
82             session.close();
83         } catch (Exception e) {
84             e.printStackTrace();
85         }
86         System.out.println("findByName()...end");
87     }
88 }

再执行UserServlet,查看数据库,发现数据添加成功了。

细心的同学会发现,我们构造要添加的User对象时,id属性的值是0,但是数据库中实际增加的记录的id值不是0,这是因为,在创建表是,我们让id字段是自增长的,这样的话,不论你指定添加数据的id值是几,数据库都是按照自增长的方式来填写id字段。

还有,我们在映射文件UserMapper.xml中定义的<insert>标签的参数是 #{name}, #{password}, #{email}, #{birthday}

<insert id="insertUser">
    insert into tb_users (name, password, email, birthday)
    values (#{name}, #{password}, #{email}, #{birthday})
</insert>

而我们在调用UserMapper接口的insertUser()方法时传入的是User对象。

int i = userMapper.insertUser(new User(0, "zhaoliu", "111", "zhaoliu@163.com", "1981-08-11"));

这里的参数是怎么传值的呢?

我们在映射文件UserMapper.xml中定义SQL语句时,只要按照User类的属性名称设置参数即可。

MyBatis会根据User类的对象的属性名,将参数值传入SQL语句。

(二)删除

根据用户名删除一条用户信息。

1、在UserMapper.java接口中定义删除方法

int deleteUser(String name);

2、在UserMapper.xml中对接口方法进行“实现”

<delete id="deleteUser">
    delete
    from tb_users
    where name = #{name}
</delete>

3、调用添加方法,测试效果

在UserServlet中定义deleteUser()方法

private void deleteUser(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    System.out.println("deleteUser()...start");
    String resouces = "mybatis-config.xml";
    // 创建流
    Reader reader = null;
    // 读取mybatis-config.xml文件内容到reader对象中
    reader = Resources.getResourceAsReader(resouces);
    // 初始化MyBatis数据源,创建SqlSessionFactory类的实例
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    // 创建SqlSession类的实例
    SqlSession session = sqlSessionFactory.openSession();
    // 调用SqlSession的getMapper方法,获取指定接口的实现类对象
    UserMapper userMapper = session.getMapper(UserMapper.class);

    int i = userMapper.deleteUser("lisi");
    session.commit(); // 提交事务
    resp.getWriter().println("删除" + i + "条记录");
    // 关闭SqlSession对象
    session.close();
    System.out.println("deleteUser()...end");
}

在doGet()方法中调用该方法

deleteUser(req, resp);

运行程序,结果如下:

(三)修改

根据用户名修改用户的密码、email等信息。

1、在UserMapper.java接口中定义更新方法

int updateUser(User user); 

2、在UserMapper.xml中对接口方法进行“实现”

<update id="updateUser">
    update tb_users
    set password = #{password},
        email    = #{email},
        birthday = #{birthday}
    where name = #{name}
</update>

3、调用添加方法,测试效果

在UserServlet中定义updateUser()方法

private void updateUser(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    System.out.println("updateUser()...start");
    String resouces = "mybatis-config.xml";
    // 创建流
    Reader reader = null;
    // 读取mybatis-config.xml文件内容到reader对象中
    reader = Resources.getResourceAsReader(resouces);
    // 初始化MyBatis数据源,创建SqlSessionFactory类的实例
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    // 创建SqlSession类的实例
    SqlSession session = sqlSessionFactory.openSession();
    // 调用SqlSession的getMapper方法,获取指定接口的实现类对象
    UserMapper userMapper = session.getMapper(UserMapper.class);

    int i = userMapper.updateUser(new User(0, "zhaoliu", "222", "zhaoliu@sina.com", "1981-08-11"));
    session.commit(); // 提交事务
    resp.getWriter().println("更新" + i + "条记录");
    // 关闭SqlSession对象
    session.close();
    System.out.println("updateUser()...end");
}

在doGet()方法中调用该方法

updateUser(req, resp);

运行程序,结果如下:

 

(四)查询----查询所有记录

查询所有用户信息。

1、在UserMapper.java接口中定义查询方法

List<User> findAll();

注意,因为我们是查询所有用户,所以,该方法的返回值类型是一个List集合,集合中的每个元素是一个User类的对象。

2、在UserMapper.xml中对接口方法进行“实现”

<select id="findAll" resultType="com.sdbi.pojo.User">
    SELECT *
    FROM tb_users
</select>

注意,这里的resultType的值是返回结果的对象类型,也就是我们的实体类User,不是List集合。

如果要明确指出集合类型,我们需要使用resultSets属性(resultSets="java.util.List"),当然resultSets属性也可以省略不写。

 

3、调用添加方法,测试效果

在UserServlet.java类中定义findAll()方法

private void findAll(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    System.out.println("findAll()...start");
    String resouces = "mybatis-config.xml";
    // 创建流
    Reader reader = null;
    try {
        // 读取mybatis-config.xml文件内容到reader对象中
        reader = Resources.getResourceAsReader(resouces);
    } catch (IOException e) {
        e.printStackTrace();
    }
    // 初始化MyBatis数据源,创建SqlSessionFactory类的实例
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    // 创建SqlSession类的实例
    SqlSession session = sqlSessionFactory.openSession();
    // 调用SqlSession的getMapper方法,获取指定接口的实现类对象
    UserMapper userMapper = session.getMapper(UserMapper.class);
    // 传入参数,调用实现类对象的方法,返回结果User
    List<User> list = userMapper.findAll();
    for (User user : list) {
        // 输出结果
        System.out.println(user.toString());
        resp.getWriter().println(user.toString() + "<br />");
    }
    // 关闭SqlSession对象
    session.close();
    System.out.println("findAll()...end");
}

在doGet()方法中调用该方法:

findAll(req, resp);

运行请求UserServlet,显示数据库中所有的用户数据。

问题一:如果我们定义的实体类中属性(成员变量)的名称和数据库中的字段名不一致,怎么办?

来实践一下。

重新定义个Student类,让类的属性名称和数据表中的字段名不一致。

package com.sdbi.pojo;

public class Student {
    private int stuId;
    private String stuName;
    private String stuPwd;
    private String stuEmail;
    private String stuBirthdate;

    @Override
    public String toString() {
        return "Student{" +
                "stuId=" + stuId +
                ", stuName='" + stuName + '\'' +
                ", stuPwd='" + stuPwd + '\'' +
                ", stuEmail='" + stuEmail + '\'' +
                ", stuBirthdate='" + stuBirthdate + '\'' +
                '}';
    }
}

在映射文件UserMapper.xml中再增加一个<select>,将resultType指定为刚定义的Student类。

<select id="findAllStudent" resultType="com.sdbi.pojo.Student">
    SELECT *
    FROM tb_users
</select>

在相应的UserMapper.java接口中,也声明一个findAllStudent()方法:

List<Student> findAllStudent();

这时,我们再UserServlet中新定义一个findAllStudent()方法,并且在doGet()中调用该方法。

private void findAllStudent(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    System.out.println("findAllStudent()...start");
    String resouces = "mybatis-config.xml";
    // 创建流
    Reader reader = null;
    try {
        // 读取mybatis-config.xml文件内容到reader对象中
        reader = Resources.getResourceAsReader(resouces);
    } catch (IOException e) {
        e.printStackTrace();
    }
    // 初始化MyBatis数据源,创建SqlSessionFactory类的实例
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    // 创建SqlSession类的实例
    SqlSession session = sqlSessionFactory.openSession();
    // 调用SqlSession的getMapper方法,获取指定接口的实现类对象
    UserMapper userMapper = session.getMapper(UserMapper.class);
    // 传入参数,调用实现类对象的方法,返回结果User
    List<Student> list = userMapper.findAllStudent();
    for (Student student : list) {
        // 输出结果
        System.out.println(student.toString());
        resp.getWriter().println(student.toString() + "<br />");
    }
    // 关闭SqlSession对象
    session.close();
    System.out.println("findAllStudent()...end");
}

运行程序,会出现下列错误。

如何解决呢?

方法一:

在映射文件UserMapper.xml中,编写SQL语句时给字段添加别名:

<select id="findAllStudent" resultType="com.sdbi.pojo.Student">
    SELECT id stuId, name stuName, password stuPwd, email stuEmail, birthday stuBirthdate
    FROM tb_users
</select>

其中,SQL语句中定义别名的“AS”可以省略。

方法二:(推荐使用方法二)

在映射文件UserMapper.xml中,使用<resultMap>添加对应关系:

<resultMap id="studentMap" type="com.sdbi.pojo.Student">
    <id column="id" property="stuId"/>
    <result column="name" property="stuName"/>
    <result column="password" property="stuPwd"/>
    <result column="email" property="stuEmail"/>
    <result column="birthday" property="stuBirthdate"/>
</resultMap>

<select id="findAllStudent" resultMap="studentMap">
    SELECT *
    FROM tb_users
</select>

这时,<select>标签中的resultType属性可以省略不写,只使用resultMap属性来指定上面定义的<resultMap>标签的id值。 

<resultMap>标签的子元素<id>和<result>都是映射单列值到一个属性或字段的简单数据类型,<id>代表resultMap的主键,而<result>代表其他属性。

他俩的区别是:<id>是作为唯一标识的,当和其他对象实例对比的时候,这个<id>很有用,尤其是应用到缓存和内嵌的结果映射。

我们推荐使用第二种方法,使用<resultMap>添加对应关系。因为这个对应关系建立一次之后,在其他查询时可以通过id属性重复使用。

问题二:这里必须注意:column属性值是数据表中的字段名,property属性值是实体类的属性名。

如果名称对应错误,则会报如下错误。

所以一定要对应正确才行。

(五)查询----查询一条记录

根据用户名或者id查询一个用户的信息。

UserMapper.java接口

User findByName(String name);

User findById(Integer id);

UserMapper.xml映射文件

需要指定resultType属性
<select id="findByName" resultType="com.sdbi.pojo.User">
    SELECT *
    FROM tb_users
    where name = #{name}
</select>

<select id="findById" resultType="com.sdbi.pojo.User">
    SELECT *
    FROM tb_users
    where id = #{id}
</select>

在UserServlet中调用

private void findByName(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    System.out.println("findByName()...start");
    String resouces = "mybatis-config.xml";
    // 创建流
    Reader reader = null;
    try {
        // 读取mybatis-config.xml文件内容到reader对象中
        reader = Resources.getResourceAsReader(resouces);
    } catch (IOException e) {
        e.printStackTrace();
    }
    // 初始化MyBatis数据源,创建SqlSessionFactory类的实例
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    // 创建SqlSession类的实例
    SqlSession session = sqlSessionFactory.openSession();
    // 调用SqlSession的getMapper方法,获取指定接口的实现类对象
    UserMapper userMapper = session.getMapper(UserMapper.class);
    // 传入参数,调用实现类对象的方法,返回结果User
    User user = userMapper.findByName("zhangsan");
//        User user = userMapper.findById(6);
    // 输出结果
    System.out.println(user.toString());
    resp.getWriter().println(user.toString());
    // 关闭SqlSession对象
    session.close();
    System.out.println("findByName()...end");
}

(六)查询----多参数查询(分页查询)

分页查询(参数 start , pageSize)

UserMapper.java接口

在UserMapper接口中定义操作方法,如果方法有多个参数,使用@Param注解声明参数的别名。

List<User> findByPage(@Param("start") int start, @Param("pageSize") int pageSize);

注意,别名是在UserMapper.xml映射文件中使用的参数名称。

如果在UserMapper.java接口的操作方法中没有通过@Param指定参数别名,在UserMapper.xml映射文件的SQL中也可以通过“arg0,arg1,...”或者“param1,param2,...”获取参数。

UserMapper.xml映射文件

<select id="findByPage" resultType="com.sdbi.pojo.User">
    SELECT *
    FROM tb_users LIMIT #{start}, #{pageSize}
</select>

在UserServlet中调用

private void findByPage(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    System.out.println("findByPage()...start");
    String resouces = "mybatis-config.xml";
    // 创建流
    Reader reader = null;
    try {
        // 读取mybatis-config.xml文件内容到reader对象中
        reader = Resources.getResourceAsReader(resouces);
    } catch (IOException e) {
        e.printStackTrace();
    }
    // 初始化MyBatis数据源,创建SqlSessionFactory类的实例
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    // 创建SqlSession类的实例
    SqlSession session = sqlSessionFactory.openSession();
    // 调用SqlSession的getMapper方法,获取指定接口的实现类对象
    UserMapper userMapper = session.getMapper(UserMapper.class);
    // 传入参数,调用实现类对象的方法,返回结果User
    List<User> list = userMapper.findByPage(0, 3);
    for (User user : list) {
        // 输出结果
        System.out.println(user.toString());
        resp.getWriter().println(user.toString() + "<br />");
    }
    // 关闭SqlSession对象
    session.close();
    System.out.println("findByPage()...end");
}

(七)查询----查询总记录数

查询用户总数。

UserMapper.java接口

int getCount();

UserMapper.xml映射文件

通过resultType指定当前操作的返回类型为int

<select id="getCount" resultType="int">
    SELECT count(*)
    FROM tb_users
</select>

在UserServlet中调用

private void getCount(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    System.out.println("getCount()...start");
    String resouces = "mybatis-config.xml";
    // 创建流
    Reader reader = null;
    try {
        // 读取mybatis-config.xml文件内容到reader对象中
        reader = Resources.getResourceAsReader(resouces);
    } catch (IOException e) {
        e.printStackTrace();
    }
    // 初始化MyBatis数据源,创建SqlSessionFactory类的实例
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    // 创建SqlSession类的实例
    SqlSession session = sqlSessionFactory.openSession();
    // 调用SqlSession的getMapper方法,获取指定接口的实现类对象
    UserMapper userMapper = session.getMapper(UserMapper.class);
    // 传入参数,调用实现类对象的方法,返回结果User
    int count = userMapper.getCount();
    // 输出结果
    System.out.println("总记录数是:" + count);
    resp.getWriter().println("总记录数是:" + count);
    // 关闭SqlSession对象
    session.close();
    System.out.println("getCount()...end");
}

(八)添加操作回填生成的主键

获取新插入记录的id值。

如果想得到新插入记录的id值,有人认为可以在插入新记录后通过查询语句获取最大的id值,但是如果在我们插入新数据后,还没来得及查询最大id值时,有另外的客户端也插入了一条新记录,这样是不是就无法获取我们刚刚插入新纪录的id值了?所以这种获取最大id值的方法不行。

我们要利用<insert>标签的两个新属性

  • useGeneratedKeys:设置添加操作是否需要回填生成的主键
  • keyProperty:设置回填的主键值赋值到参数对象的哪个属性

UserMapper.xml映射文件

<!-- useGeneratedKeys 设置添加操作是否需要回填生成的主键 -->
<!-- keyProperty 设置回填的主键值赋值到参数对象的哪个属性 -->
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    insert into tb_users (name, password, email, birthday)
    values (#{name}, #{password}, #{email}, #{birthday})
</insert>

在UserServlet中调用

private void insertUser(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    System.out.println("insertUser()...start");
    String resouces = "mybatis-config.xml";
    // 创建流
    Reader reader = null;
    // 读取mybatis-config.xml文件内容到reader对象中
    reader = Resources.getResourceAsReader(resouces);
    // 初始化MyBatis数据源,创建SqlSessionFactory类的实例
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    // 创建SqlSession类的实例
    SqlSession session = sqlSessionFactory.openSession();
    // 调用SqlSession的getMapper方法,获取指定接口的实现类对象
    UserMapper userMapper = session.getMapper(UserMapper.class);
    User user = new User(0, "xiaoliu", "555", "xiaoliu@163.com", "1981-08-11");
    int i = userMapper.insertUser(user);
    System.out.println(user.toString());
    session.commit(); // 提交事务
    resp.getWriter().println("插入" + i + "条记录");
    // 关闭SqlSession对象
    session.close();
    System.out.println("insertUser()...end");
}

运行程序,发现user对象的id属性不再是0,而是数据库中新插入的这条记录的id。

二、MyBatis工具类封装

我们发现UserServlet.java中的insertUser()、deleteUser()、updateUser()、findByName()等方法都有获得Reader->SqlSessionFactory->SqlSession等对象的过程。

我们可以定义一个工具类,将这些步骤封装起来。

新建com.sdbi.util包,在该包内新建MyBatisUtil.java类。

定义一个getSqlSession()方法。

public class MyBatisUtil {
    public static SqlSession getSqlSession() {
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader("mybatis-config.xml");
        } catch (IOException e) {
            e.printStackTrace();
        }
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(reader);
        return factory.openSession();
    }
}

我们发现每次调用这个方法都会加载mybatis-config.xml文件,这样浪费资源,也很不合理。

因此,我们考虑使用单例模式创建SqlSessionFactory对象(SqlSessionFactory对象只要有一个就够了),这样可以使用静态代码快来创建SqlSessionFactory对象。

修改MyBatisUtil类。

public class MyBatisUtil {
    private static final SqlSessionFactory factory;

    static {
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader("mybatis-config.xml");
        } catch (IOException e) {
            e.printStackTrace();
        }
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        factory = builder.build(reader);
    }

    public static SqlSession getSqlSession() {        
        return factory.openSession();
    }
}

我们再来考虑,这里的getSqlSession()方法是不是会被多个客户端同时调用,所以我们需要给它添加一把本地锁,我们要使用ThreadLocal<T>来管理SqlSession。

public class MyBatisUtil {
    private static final SqlSessionFactory factory;
    private static final ThreadLocal<SqlSession> local = new ThreadLocal<SqlSession>();

    static {
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader("mybatis-config.xml");
        } catch (IOException e) {
            e.printStackTrace();
        }
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        factory = builder.build(reader);
    }

    public static SqlSession getSqlSession() {
        SqlSession sqlSession = local.get();
        if (sqlSession == null) {
            sqlSession = factory.openSession();
            // 如果原先local中没有,我们新建之后要把它放到local中
            local.set(sqlSession);
        }
        return sqlSession;
    }
}

回来再看UserServlet中的insertUser()、deleteUser()、updateUser()、findByName()、findAll()等方法,我们以findAll()方法为例来修改吧。

private void findAll(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    System.out.println("findAll()...start");
    // 创建SqlSession类的实例
    SqlSession session = MyBatisUtil.getSqlSession();
    // 调用SqlSession的getMapper方法,获取指定接口的实现类对象
    UserMapper userMapper = session.getMapper(UserMapper.class);
    // 传入参数,调用实现类对象的方法,返回结果User
    List<User> list = userMapper.findAll();
    for (User user : list) {
        // 输出结果
        System.out.println(user.toString());
        resp.getWriter().println(user.toString() + "<br />");
    }
    // 关闭SqlSession对象
    session.close();
    System.out.println("findAll()...end");
}

另外,我们发现,我们在Servlet中基本都要获取XxxMapper接口的实现类对象。 

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

我们在MyBatisUtil工具类中定义getMapper方法,返回值定义为泛型T,这样可以获取任意类型的Mapper对象。

另外,我们有时还需要SqlSessionFactory对象,我们再多定义一个getFactory()方法。

package com.sdbi.util;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.Reader;

public class MyBatisUtil {
    private static final SqlSessionFactory factory;
    private static final ThreadLocal<SqlSession> local = new ThreadLocal<SqlSession>();

    static {
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader("mybatis-config.xml");
        } catch (IOException e) {
            e.printStackTrace();
        }
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        factory = builder.build(reader);
    }

    public static SqlSessionFactory getFactory() {
        return factory;
    }

    public static SqlSession getSqlSession() {
        SqlSession sqlSession = local.get();
        if (sqlSession == null) {
            sqlSession = factory.openSession();
            // 如果原先local中没有,我们新建之后要把它放到local中
            local.set(sqlSession);
        }
        return sqlSession;
    }

    public static <T extends Object> T getMapper(Class<T> c) {
        return getSqlSession().getMapper(c);
    }
}

在UserServlet中findAll()方法调用。

private void findAll(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    System.out.println("findAll()...start");
    UserMapper userMapper = MyBatisUtil.getMapper(UserMapper.class);
    // 传入参数,调用实现类对象的方法,返回结果User
    List<User> list = userMapper.findAll();
    for (User user : list) {
        // 输出结果
        System.out.println(user.toString());
        resp.getWriter().println(user.toString() + "<br />");
    }
    System.out.println("findAll()...end");
}

三、事务管理

我们知道增、删、改操作之后,都要进行事务的提交,我们需要调用SqlSession的commit()方法。下面我们就来详细学习一下MyBatis的事务管理。

(一)手动提交事务

  • sqlSession.commit();提交事务

  • sqlSession.rollback();事务回滚

当我们获取SqlSession对象时,就默认开启了事务。我们以UserServlet.java中的insertUser()方法为例。

private void insertUser(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    System.out.println("insertUser()...start");
    // 当我们获取SqlSession对象时,就默认开启了事务
    SqlSession session = MyBatisUtil.getSqlSession();
    try {
        UserMapper userMapper = session.getMapper(UserMapper.class);
        User user = new User(0, "xiaoguo", "666", "xiaoguo@163.com", "1997-08-11");
        int i = userMapper.insertUser(user);
        System.out.println(user.toString());
//        int a = 10 / 0; // 为了测试,在提交事务前,故意制造一个异常
        session.commit(); // 提交事务
        resp.getWriter().println("插入" + i + "条记录");
    } catch (Exception e) {
        session.rollback(); // 当出现异常,事务回滚
    }
    // 关闭SqlSession对象
    session.close();
    System.out.println("insertUser()...end");
}

我们在提交事务之前,为了测试,通过数字除0,故意制造了一个异常,来查看插入数据是否成功。

(二)自动提交事务

通过SqlSessionFactory调用openSession方法获取SqlSession对象时,可以通过参数设置事务是否自动提交:

  • 如果参数设置为true,表示自动提交事务: factory.openSession(true);

  • 如果参数设置为false,或者不设置参数,表示手动提交:factory.openSession();/factory.openSession(false);

 修改MyBatisUtil工具类:

1.给getSqlSession()方法增加一个boolean类型的参数,用于指定是否手动提交,并将该方法设置为private,不对外提供。

2.修改getMapper()方法,将该方法定义为自动提交事务

3.新定义一个getSqlSession()方法,将该方法定义为手动提交事务

在业务逻辑层,需要手动事务管理时,我们使用getSqlSession()方法,需要使用自动事务管理时,直接使用getMapper()方法。

package com.sdbi.util;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.Reader;

public class MyBatisUtil {
    private static final SqlSessionFactory factory;
    private static final ThreadLocal<SqlSession> local = new ThreadLocal<SqlSession>();

    static {
        Reader reader = null;
        try {
            reader = Resources.getResourceAsReader("mybatis-config.xml");
        } catch (IOException e) {
            e.printStackTrace();
        }
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        factory = builder.build(reader);
    }

    public static SqlSessionFactory getFactory() {
        return factory;
    }

    // 获取SqlSession对象时,可以选择是否手动提交事务
    private static SqlSession getSqlSession(boolean isAutoCommit) {
        SqlSession sqlSession = local.get();
        if (sqlSession == null) {
            sqlSession = factory.openSession(isAutoCommit); // true自动提交,false手动提交
            // 如果原先local中没有,我们新建之后要把它放到local中
            local.set(sqlSession);
        }
        return sqlSession;
    }

    // 手动事务管理
    public static SqlSession getSqlSession() {
        return getSqlSession(false);
    }

    // 自动事务提交
    public static <T extends Object> T getMapper(Class<T> c) {
        return getSqlSession(true).getMapper(c);
    }
}

四、MyBatis主配置文件

mybatis-config.xml是MyBatis框架的核心配置文件,主要用于配置MyBatis数据源及属性信息。

MyBatis的核心配置文件中主要包括以下配置标记及层级关系:

 

  • configuration(配置)
    • properties(属性)
    • settings(设置)
    • typeAliases(类型别名)
    • typeHandlers(类型处理器)
    • objectFactory(对象工厂)
    • plugins(插件)
    • environments(环境配置)
      • environment(环境变量)
        • transactionManager(事务管理器)
        • dataSource(数据源)
    • databaseIdProvider(数据库厂商标识)
    • mappers(映射器)

在编写配置时要严格按照以上顺序排列,不然会报错!

(一)properties标签

用于设置键值对,或者加载属性文件。

用法一:设置键值对。

<properties>
    <property name="mysql.driver" value="com.mysql.jdbc.Driver"/>
    <property name="mysql.url" value="jdbc:mysql://localhost:3306/db_test?characterEncoding=utf8"/>
    <property name="mysql.username" value="root"/>
    <property name="mysql.password" value="1234"/>
</properties>
<environments default="mysql">
    <environment id="mysql">
        <transactionManager type="JDBC"/>
        <!-- 数据库连接配置,引用properties标签中的内容 -->
        <dataSource type="POOLED">
            <property name="driver" value="${mysql.driver}"/>
            <property name="url" value="${mysql.url}"/>
            <property name="username" value="${mysql.username}"/>
            <property name="password" value="${mysql.password}"/>
        </dataSource>
    </environment>
</environments>

但是这种用法不推荐,把数据库连接的定义和使用写到一个文件里,不够灵活。

用法二:

在resources目录下创建db.properties文件,配置键值对如下:

mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/db_test?characterEncoding=utf8
mysql.username=root
mysql.password=1234

在mybatis-config.xml中通过properties标签引用db.properties文件;引入之后,在配置environment时可以直接使用db.properties的key获取对应的value。

<!-- 加载属性文件 -->
<properties resource="db.properties"/>
<environments default="mysql">
    <environment id="mysql">
        <transactionManager type="JDBC"/>
        <!-- 数据库连接配置,db.properties文件中的内容 -->
        <dataSource type="POOLED">
            <property name="driver" value="${mysql.driver}"/>
            <property name="url" value="${mysql.url}"/>
            <property name="username" value="${mysql.username}"/>
            <property name="password" value="${mysql.password}"/>
        </dataSource>
    </environment>
</environments>

(二)settings标签

settings标签用于设置mybatis的属性。例如缓存、延迟加载、命名规则等一系列控制性参数。

<!--设置mybatis的属性-->
<settings>
    <!-- 启动二级缓存-->
    <setting name="cacheEnabled" value="true"/>
    <!-- 启动延迟加载 -->
    <setting name="lazyLoadingEnabled" value="true"/>
</settings>

(三)typeAliases标签

typeAliases标签用于给实体类取别名,在映射文件中可以直接使用别名来替代实体类的全限定名,别名可以减少全限定类名的冗余。

<!--typeAliases标签用于给实体类取别名,在映射文件中可以直接使用别名来替代实体类的全限定名-->
<typeAliases>
    <typeAlias type="com.sdbi.pojo.Student" alias="Student"></typeAlias>
    <typeAlias type="com.sdbi.pojo.User" alias="User"></typeAlias>
</typeAliases>

(四)plugins标签

plugins标签用于配置MyBatis插件。插件可以增强MyBatis功能,比如进行sql增强,打印日志,异常处理以及分页插件等。

<!--plugins标签,用于配置MyBatis插件(分页插件)-->
<plugins>
    <plugin interceptor="com.github.pagehelper.PageHelper"></plugin>
</plugins>

(五)environments标签

environments标签可以为MyBatis配置数据环境,可以配置多种数据源。

<environments default="mysql">
    <environment id="mysql">
        <!-- transactionManager标签用于配置数据库管理方式-->
        <!--   type="JDBC"  可以进行事务的提交和回滚操作-->
        <!--   type="MANAGED" 依赖容器完成事务管理,本身不进行事务的提交和回滚操作-->
        <transactionManager type="JDBC"/>
        <!-- 数据库连接配置,db.properties文件中的内容 -->
        <!--dataSource标签就是用来配置数据库连接信息 POOLED|UNPOOLED -->
        <dataSource type="POOLED">
            <property name="driver" value="${mysql.driver}"/>
            <property name="url" value="${mysql.url}"/>
            <property name="username" value="${mysql.username}"/>
            <property name="password" value="${mysql.password}"/>
        </dataSource>
    </environment>
</environments>

(六)mappers标签

mappers标签用于用于注册映射文件或持久层接口,只有注册的映射文件才能使用,一般有以下四种方式可以完成注册:

1、使用相对路径注册映射文件--resource属性

<mappers>
    <mapper resource="mapper/UserMapper.xml"/>
</mappers>

2、使用绝对路径注册映射文件--url属性

<mappers>
    <mapper url="file:\\\D:\workspace\project_idea\MyBatisTest\src\main\resources\mapper\UserMapper.xml"/>
</mappers>

<mappers>
    <mapper url="file:///D:/workspace/project_idea/MyBatisTest/src/main/resources/mapper/UserMapper.xml"/>
</mappers>

在windows系统中,斜杠向左向右都行。

3、注册持久层接口--class属性

注意:需要对应的XxxMapper.xml与接口XxxMapper.java要处于同一包下才可以,且xml文件名与接口名要相同,xml文件中的namespace必须是对应接口的全包名。

<mappers>
    <mapper class="com.sdbi.mapper.UserMapper"/>
</mappers> 

4、注册一个包下的所有持久层接口--package标签

<mappers>
    <package name="com.sdbi.mapper"/>
</mappers>

注意:如果将XxxMapper.xml文件和XxxMapper.java文件放到一个文件夹下,我们需要修改pom.xml文件,将java和resources文件夹下的xml文件和properties文件都打包发布。否则会报错org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)。

 

<build>
    <resources>
        <!--将src/main/java目录下的所有xml文件都作为项目的资源文件,编译打包时会进行预编译并打包进去,-->
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
        </resource>
        <!--将src/main/resources目录下的所有.xml文件和*.properties作为项目的资源文件,编译打包时会进行预编译并打包进去-->
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.xml</include>
                <include>**/*.properties</include>
            </includes>
        </resource>
    </resources>
</build>

五、映射文件

(一)mapper根标签

mapper文件相当于DAO层Mapper接口的“实现类”,namespace属性要指定实现Mapper接口的全限定名。

(二)insert标签

声明添加操作,对应的SQL:INSERT INTO tb_users (name, password, email, birthday) VALUES (#{name}, #{password}, #{email}, #{birthday})

常用属性

  • id属性:绑定对应DAO层接口中的方法
  • parameterType属性:用于指定接口中对应方法的参数类型(可省略)
  • useGeneratedKeys属性:设置添加操作是否需要回填生成的主键
  • keyProperty属性:指定回填的id设置到参数对象中的哪个属性
  • timeout属性:设置此操作的超时时间,如果不设置则一直等待
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO tb_users (name, password, email, birthday)
    VALUES (#{name}, #{password}, #{email}, #{birthday})
</insert>

(三)delete标签

声明删除操作,对应的SQL:DELETE FROM tb_users WHERE name = #{name}

常用属性

  • id属性:绑定对应DAO层接口中的方法
  • parameterType属性:用于指定接口中对应方法的参数类型(可省略)
  • timeout属性:设置此操作的超时时间,如果不设置则一直等待
<delete id="deleteUser">
    DELETE
    FROM tb_users
    WHERE name = #{name}
</delete>

(四)update标签

声明修改操作,对应的SQL:UPDATE tb_users SET password = #{password}, email = #{email}, birthday = #{birthday} WHERE name = #{name}

常用属性

  • id属性:绑定对应DAO层接口中的方法
  • parameterType属性:用于指定接口中对应方法的参数类型(可省略)
  • timeout属性:设置此操作的超时时间,如果不设置则一直等待
<update id="updateUser">
    UPDATE tb_users
    SET password = #{password},
        email    = #{email},
        birthday = #{birthday}
    WHERE name = #{name}
</update>

(五)select标签

声明查询操作,对应的SQL:SELECT * FROM tb_users

常用属性

  • id属性:指定绑定方法的方法名

  • parameterType属性:设置参数类型(可省略)

  • resultType属性,指定当前sql返回数据封装的对象类型(实体类)

  • resultMap属性,指定从数据表到实体类的字段和属性的对应关系

  • useCache属性:指定此查询操作是否需要缓存

  • timeout属性:设置超时时间

<select id="findByName" parameterType="String" resultType="com.sdbi.pojo.User">
    SELECT *
    FROM tb_users
    WHERE name = #{name}
</select>

<resultMap id="studentMap" type="com.sdbi.pojo.Student">
    <id column="id" property="stuId"/>
    <result column="name" property="stuName"/>
    <result column="password" property="stuPwd"/>
    <result column="email" property="stuEmail"/>
    <result column="birthday" property="stuBirthdate"/>
</resultMap>

<select id="findAllStudent" resultMap="studentMap">
    SELECT *
    FROM tb_users
</select>

(六)resultMap标签

resultMap标签用于定义实体类与数据表的映射关系(ORM)

<!-- resultMap标签用于定义实体类与数据表的映射关系(ORM) -->
<resultMap id="studentMap" type="com.sdbi.pojo.Student">
    <id column="id" property="stuId"/>
    <result column="name" property="stuName"/>
    <result column="password" property="stuPwd"/>
    <result column="email" property="stuEmail"/>
    <result column="birthday" property="stuBirthdate"/>
</resultMap>

(七)sql和include

定义和引用SQL片段

<sql id="wanglaoji">id , name , password , email , birthday</sql>

<select id="listStudents" resultMap="studentMap">
    select <include refid="wanglaoji"/> from tb_users
</select>

六、分页插件

分页插件(pagehelper)是一个独立于MyBatis框架之外的第三方插件。

(一)添加分页插件的依赖

在pom.xml文件中添加依赖

<!-- pagehelper分页插件 -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.3.2</version>
</dependency>

(二)配置插件

在MyBatis的核心配置文件mybatis-config.xml中通过<plugins>标签进行配置。注意位置,放在environments标签前面。

属性interceptor,拦截器。

<!--plugins标签,用于配置MyBatis插件(分页插件)-->
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>

(三)分页实现

对用户信息进行分页查询

private void findByPage(HttpServletRequest req, HttpServletResponse resp) throws IOException {
    System.out.println("findByPage()...start");
    UserMapper userMapper = MyBatisUtil.getMapper(UserMapper.class);
    // 开始分页,pageNum – 页码,pageSize – 每页显示数量
    PageHelper.startPage(2, 3);
    List<User> list = userMapper.findAll();
    PageInfo<User> pageInfo = new PageInfo<User>(list);
    for (User user : list) {
        // 输出结果
        System.out.println(user.toString());
        resp.getWriter().println(user.toString() + "<br />");
    }
    System.out.println(pageInfo.toString());
    System.out.println("findByPage()...end");
}

我们使用断点调试和控制台输出pageInfo对象的信息,可以看到pageInfo里包含了很多分页信息:

 

posted @ 2023-03-20 08:50  熊猫Panda先生  阅读(1444)  评论(0编辑  收藏  举报