02.MyBatis的操作
mapper.UserMapper">
<mapper namespace="com.sdbi.添加一条用户信息。
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接口
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。
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对象。
修改
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,故意制造了一个异常,来查看插入数据是否成功。
(二)自动提交事务
-
如果参数设置为true,表示自动提交事务: factory.openSession(true);
-
修改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-config.xml是MyBatis框架的核心配置文件,主要用于配置MyBatis数据源及属性信息。
MyBatis的核心配置文件中主要包括以下配置标记及层级关系:
- configuration(配置)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- environment(环境变量)
- 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>
但是这种用法不推荐,把数据库连接的定义和使用写到一个文件里,不够灵活。
用法二:
mysql.driver=com.mysql.jdbc.Driver mysql.url=jdbc:mysql://localhost:3306/db_test?characterEncoding=utf8 mysql.username=root mysql.password=1234
properties标签引用
<!-- 加载属性文件 --> <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>
(五)
可以为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>
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文件相当于DAO层Mapper接口的“实现类”,namespace属性要指定实现
(二)
声明添加操作,对应的SQL:INSERT INTO tb_users (name, password, email, birthday) VALUES (#{name}, #{password}, #{email}, #{birthday})
常用属性
- id属性:绑定对应DAO层接口中的方法
- parameterType属性:用于指定接口中对应方法的参数类型(可省略)
- useGeneratedKeys属性:设置添加操作是否需要回填生成的主键
- keyProperty属性:指定回填的id设置到参数对象中的哪个属性
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id"> INSERT INTO tb_users (name, password, email, birthday) VALUES (#{name}, #{password}, #{email}, #{birthday}) </insert>
(三)
声明删除操作,对应的SQL:DELETE FROM tb_users WHERE name = #{name}
常用属性
- id属性:绑定对应DAO层接口中的方法
- parameterType属性:用于指定接口中对应方法的参数类型(可省略)
<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属性:用于指定接口中对应方法的参数类型(可省略)
<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属性:设置参数类型(可省略)
-
-
-
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标签用于定义实体类与数据表的映射关系(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 id="wanglaoji">id , name , password , email , birthday</sql> <select id="listStudents" resultMap="studentMap"> select <include refid="wanglaoji"/> from tb_users </select>
分页插件(pagehelper)是一个独立于MyBatis框架之外的第三方插件。
(一)添加分页插件的依赖
<!-- 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里包含了很多分页信息: