目录
2.8 MyBatis工具类SqlSessionUtil的封装

- // ......
- // sql语句写死在java程序中
- String sql = "insert into t_user(id,idCard,username,password,birth,gender,email,city,street,zipcode,phone,grade) values(?,?,?,?,?,?,?,?,?,?,?,?)";
- PreparedStatement ps = conn.prepareStatement(sql);
- // 繁琐的赋值:思考一下,这种有规律的代码能不能通过反射机制来做自动化。
- ps.setString(1, "1");
- ps.setString(2, "123456789");
- ps.setString(3, "zhangsan");
- ps.setString(4, "123456");
- ps.setString(5, "1980-10-11");
- ps.setString(6, "男");
- ps.setString(7, "zhangsan@126.com");
- ps.setString(8, "北京");
- ps.setString(9, "大兴区凉水河二街");
- ps.setString(10, "1000000");
- ps.setString(11, "16398574152");
- ps.setString(12, "A");
- // 执行SQL
- int count = ps.executeUpdate();
- // ......
- // ......
- // sql语句写死在java程序中
- String sql = "select id,idCard,username,password,birth,gender,email,city,street,zipcode,phone,grade from t_user";
- PreparedStatement ps = conn.prepareStatement(sql);
- ResultSet rs = ps.executeQuery();
- List
userList = new ArrayList<>(); - // 思考以下循环中的所有代码是否可以使用反射进行自动化封装。
- while(rs.next()){
- // 获取数据
- String id = rs.getString("id");
- String idCard = rs.getString("idCard");
- String username = rs.getString("username");
- String password = rs.getString("password");
- String birth = rs.getString("birth");
- String gender = rs.getString("gender");
- String email = rs.getString("email");
- String city = rs.getString("city");
- String street = rs.getString("street");
- String zipcode = rs.getString("zipcode");
- String phone = rs.getString("phone");
- String grade = rs.getString("grade");
- // 创建对象
- User user = new User();
- // 给对象属性赋值
- user.setId(id);
- user.setIdCard(idCard);
- user.setUsername(username);
- user.setPassword(password);
- user.setBirth(birth);
- user.setGender(gender);
- user.setEmail(email);
- user.setCity(city);
- user.setStreet(street);
- user.setZipcode(zipcode);
- user.setPhone(phone);
- user.setGrade(grade);
- // 添加到集合
- userList.add(user);
- }
- // ......




只要你会JDBC,MyBatis就可以学。












- <groupId>com.donggroupId>
- <artifactId>mybatis-001-introductionartifactId>
- <version>1.0-SNAPSHOTversion>
- <packaging>jarpackaging>
- <dependency>
- <groupId>org.mybatisgroupId>
- <artifactId>mybatisartifactId>
- <version>3.5.10version>
- dependency>
- <dependency>
- <groupId>mysqlgroupId>
- <artifactId>mysql-connector-javaartifactId>
- <version>8.0.30version>
- dependency>
- "1.0" encoding="UTF-8" ?>
- configuration
- PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
- "http://mybatis.org/dtd/mybatis-3-config.dtd">
- <configuration>
- <environments default="development">
- <environment id="development">
- <transactionManager type="JDBC"/>
- <dataSource type="POOLED">
- <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
- <property name="url" value="jdbc:mysql://localhost:3306/dong"/>
- <property name="username" value="root"/>
- <property name="password" value="root"/>
- dataSource>
- environment>
- environments>
- <mappers>
-
- <mapper resource=""/>
- mappers>
- configuration>
注意1:mybatis核心配置文件的文件名不一定是mybatis-config.xml,可以是其它名字。
注意2:mybatis核心配置文件存放的位置也可以随意。这里选择放在resources根下,相当于放到了类的根路径下。
- "1.0" encoding="UTF-8" ?>
- mapper
- PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
- <mapper namespace="car">
-
- <insert id="insertCar">
- insert into t_car
- (id,car_num,brand,guide_price,produce_time,car_type)
- values
- (null,'102','丰田mirai',40.30,'2014-10-05','氢能源')
- insert>
- mapper>
注意1:sql语句最后结尾可以不写“;”
注意2:CarMapper.xml文件的名字不是固定的。可以使用其它名字。
注意3:CarMapper.xml文件的位置也是随意的。这里选择放在resources根下,相当于放到了类的根路径下。
注意4:将CarMapper.xml文件路径配置到mybatis-config.xml:
<mapper resource="CarMapper.xml"/>
- package com.dong.mybatis;
-
- import org.apache.ibatis.session.SqlSession;
- import org.apache.ibatis.session.SqlSessionFactory;
- import org.apache.ibatis.session.SqlSessionFactoryBuilder;
-
- import java.io.InputStream;
-
- /**
- * MyBatis入门程序
- * @author 老杜
- * @since 1.0
- * @version 1.0
- */
- public class MyBatisIntroductionTest {
- public static void main(String[] args) {
- // 1. 创建SqlSessionFactoryBuilder对象
- SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
- // 2. 创建SqlSessionFactory对象
- InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatis-config.xml");
- SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
- // 3. 创建SqlSession对象
- SqlSession sqlSession = sqlSessionFactory.openSession();
- // 4. 执行sql
- int count = sqlSession.insert("insertCar"); // 这个"insertCar"必须是sql的id
- System.out.println("插入几条数据:" + count);
- // 5. 提交(mybatis默认采用的事务管理器是JDBC,默认是不提交的,需要手动提交。)
- sqlSession.commit();
- // 6. 关闭资源(只关闭是不会提交的)
- sqlSession.close();
- }
- }
注意1:默认采用的事务管理器是:JDBC。JDBC事务默认是不提交的,需要手动提交。



- // 文件名是出现在程序中的,文件名如果修改了,对应这里的java程序也改一下就行了。
- InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("mybatis-config.xml");
将mybatis-config.xml文件拷贝一份放到D盘根下,然后编写以下程序:
- package com.dong.mybatis;
-
- import org.apache.ibatis.session.SqlSession;
- import org.apache.ibatis.session.SqlSessionFactory;
- import org.apache.ibatis.session.SqlSessionFactoryBuilder;
-
- import java.io.FileInputStream;
- import java.io.InputStream;
-
- /**
- * 测试mybatis核心配置文件路径问题
- * @author captain_dong
- * @since 1.0
- * @version 1.0
- */
- public class MyBatisConfigFilePath {
- public static void main(String[] args) throws Exception{
- // 1. 创建SqlSessionFactoryBuilder对象
- SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
- // 2. 创建SqlSessionFactory对象
- // 这只是一个输入流,可以自己new。
- InputStream is = new FileInputStream("D:/mybatis-config.xml");
- SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
- // 3. 创建SqlSession对象
- SqlSession sqlSession = sqlSessionFactory.openSession();
- // 4. 执行sql
- int count = sqlSession.insert("insertCar");
- System.out.println("插入几条数据:" + count);
- // 5. 提交(mybatis默认采用的事务管理器是JDBC,默认是不提交的,需要手动提交。)
- sqlSession.commit();
- // 6. 关闭资源(只关闭是不会提交的)
- sqlSession.close();
- }
- }
以上程序运行后,看到数据库表t_car中又新增一条数据,如下(成功了):

经过测试说明mybatis核心配置文件的名字是随意的,存放路径也是随意的。
虽然mybatis核心配置文件的名字不是固定的,但通常该文件的名字叫做:mybatis-config.xml
虽然mybatis核心配置文件的路径不是固定的,但通常该文件会存放到类路径当中,这样让项目的移植更加健壮。
- // 这种方式只能从类路径当中获取资源,也就是说mybatis-config.xml文件需要在类路径下。
- InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
MyBatisCompleteCodeTest.java
- package com.dong.mybatis;
-
- 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;
-
- /**
- * 比较完整的第一个mybatis程序写法
- * @author captain_dong
- * @since 1.0
- * @version 1.0
- */
- public class MyBatisCompleteCodeTest {
- public static void main(String[] args) {
- SqlSession sqlSession = null;
- try {
- // 1.创建SqlSessionFactoryBuilder对象
- SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
- // 2.创建SqlSessionFactory对象
- SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
- // 3.创建SqlSession对象
- sqlSession = sqlSessionFactory.openSession();
- // 4.执行SQL
- int count = sqlSession.insert("insertCar");
- System.out.println("更新了几条记录:" + count);
- // 5.提交
- sqlSession.commit();
- } catch (Exception e) {
- // 回滚
- if (sqlSession != null) {
- sqlSession.rollback();
- }
- e.printStackTrace();
- } finally {
- // 6.关闭
- if (sqlSession != null) {
- sqlSession.close();
- }
- }
- }
- }
运行后数据库表的变化:

- <dependency>
- <groupId>junitgroupId>
- <artifactId>junitartifactId>
- <version>4.13.2version>
- <scope>testscope>
- dependency>
- // 测试用例
- public class CarMapperTest{
-
- // 测试方法
- @Test
- public void testInsert(){}
-
- @Test
- public void testUpdate(){}
-
- }
- package com.dong.mybatis;
-
- 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 org.junit.Test;
-
- public class CarMapperTest {
-
- @Test
- public void testInsertCar(){
- SqlSession sqlSession = null;
- try {
- // 1.创建SqlSessionFactoryBuilder对象
- SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
- // 2.创建SqlSessionFactory对象
- SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
- // 3.创建SqlSession对象
- sqlSession = sqlSessionFactory.openSession();
- // 4.执行SQL
- int count = sqlSession.insert("insertCar");
- System.out.println("更新了几条记录:" + count);
- // 5.提交
- sqlSession.commit();
- } catch (Exception e) {
- // 回滚
- if (sqlSession != null) {
- sqlSession.rollback();
- }
- e.printStackTrace();
- } finally {
- // 6.关闭
- if (sqlSession != null) {
- sqlSession.close();
- }
- }
- }
- }
执行单元测试,查看数据库表的变化:

mybatis-config.xml
- <settings>
- <setting name="logImpl" value="STDOUT_LOGGING" />
- settings>
标准日志也可以用,但是配置不够灵活,可以集成其他的日志组件,例如:log4j,logback等。
logback依赖:
- <dependency>
- <groupId>ch.qos.logbackgroupId>
- <artifactId>logback-classicartifactId>
- <version>1.2.11version>
- <scope>testscope>
- dependency>
logback.xml:
- "1.0" encoding="UTF-8"?>
-
- <configuration debug="false">
-
- <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
- <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-
- <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%npattern>
- encoder>
- appender>
-
- <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
- <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
-
- <FileNamePattern>${LOG_HOME}/TestWeb.log.%d{yyyy-MM-dd}.logFileNamePattern>
-
- <MaxHistory>30MaxHistory>
- rollingPolicy>
- <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
-
- <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%npattern>
- encoder>
-
- <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
- <MaxFileSize>100MBMaxFileSize>
- triggeringPolicy>
- appender>
-
-
- <logger name="com.apache.ibatis" level="TRACE"/>
- <logger name="java.sql.Connection" level="DEBUG"/>
- <logger name="java.sql.Statement" level="DEBUG"/>
- <logger name="java.sql.PreparedStatement" level="DEBUG"/>
-
-
- <root level="DEBUG">
- <appender-ref ref="STDOUT"/>
- <appender-ref ref="FILE"/>
- root>
-
- configuration>

SqlSessionUtil.java:
- package com.dong.mybatis.utils;
-
- import org.apache.ibatis.io.Resources;
- import org.apache.ibatis.session.SqlSession;
- import org.apache.ibatis.session.SqlSessionFactory;
- import org.apache.ibatis.session.SqlSessionFactoryBuilder;
-
- /**
- * MyBatis工具类
- *
- * @author Captain_dong
- * @version 1.0
- * @since 1.0
- */
- public class SqlSessionUtil {
- private static SqlSessionFactory sqlSessionFactory;
-
- /**
- * 类加载时初始化sqlSessionFactory对象
- */
- static {
- try {
- SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
- sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- /**
- * 每调用一次openSession()可获取一个新的会话,该会话支持自动提交。
- *
- * @return 新的会话对象
- */
- public static SqlSession openSession() {
- return sqlSessionFactory.openSession(true);
- }
- }
- @Test
- public void testInsertCar(){
- SqlSession sqlSession = SqlSessionUtil.openSession();
- // 执行SQL
- int count = sqlSession.insert("insertCar");
- System.out.println("插入了几条记录:" + count);
- sqlSession.close();
- }
分析以下SQL映射文件中SQL语句存在的问题
CarMapper.xml
- "1.0" encoding="UTF-8" ?>
- mapper
- PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-
- <mapper namespace="car">
- <insert id="insertCar">
- insert into t_car(car_num,brand,guide_price,produce_time,car_type) values('103', '奔驰E300L', 50.3, '2022-01-01', '燃油车')
- insert>
- mapper>
存在的问题是:SQL语句中的值不应该写死,值应该是用户提供的。之前的JDBC代码是这样写的:
JDBC:
- // JDBC中使用 ? 作为占位符。那么MyBatis中会使用什么作为占位符呢?
- String sql = "insert into t_car(car_num,brand,guide_price,produce_time,car_type) values(?,?,?,?,?)";
- // ......
- // 给 ? 传值。那么MyBatis中应该怎么传值呢?
- ps.setString(1,"103");
- ps.setString(2,"奔驰E300L");
- ps.setDouble(3,50.3);
- ps.setString(4,"2022-01-01");
- ps.setString(5,"燃油车");
在MyBatis中可以这样做:
在Java程序中,将数据放到Map集合中
在sql语句中使用 #{map集合的key} 来完成传值,#{} 等同于JDBC中的 ? ,#{}就是占位符
Java程序这样写:
CarMapperTest.testInsertCar():
- package com.dong.mybatis;
-
- import com.powernode.mybatis.utils.SqlSessionUtil;
- import org.apache.ibatis.session.SqlSession;
- import org.junit.Test;
-
- import java.util.HashMap;
- import java.util.Map;
-
- /**
- * 测试MyBatis的CRUD
- * @author captian_dong
- * @version 1.0
- * @since 1.0
- */
- public class CarMapperTest {
- @Test
- public void testInsertCar(){
- // 准备数据
- Map
map = new HashMap<>(); - map.put("k1", "103");
- map.put("k2", "奔驰E300L");
- map.put("k3", 50.3);
- map.put("k4", "2020-10-01");
- map.put("k5", "燃油车");
- // 获取SqlSession对象
- SqlSession sqlSession = SqlSessionUtil.openSession();
- // 执行SQL语句(使用map集合给sql语句传递数据)
- int count = sqlSession.insert("insertCar", map);
- System.out.println("插入了几条记录:" + count);
- }
- }
SQL语句这样写:
CarMapper.xml:
- "1.0" encoding="UTF-8" ?>
- mapper
- PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-
- <mapper namespace="car">
- <insert id="insertCar">
- insert into t_car(car_num,brand,guide_price,produce_time,car_type) values(#{k1},#{k2},#{k3},#{k4},#{k5})
- insert>
- mapper>
#{} 的里面必须填写map集合的key,不能随便写。
运行测试程序,查看数据库:

如果#{}里写的是map集合中不存在的key会有什么问题?
CarMapper.xml:
- "1.0" encoding="UTF-8" ?>
- mapper
- PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
-
- <mapper namespace="car">
- <insert id="insertCar">
- insert into t_car(car_num,brand,guide_price,produce_time,car_type) values(#{kk},#{k2},#{k3},#{k4},#{k5})
- insert>
- mapper>
运行程序:


通过测试,看到程序并没有报错。正常执行。不过 #{kk} 的写法导致无法获取到map集合中的数据,最终导致数据库表car_num插入了NULL。
在以上sql语句中,可以看到#{k1} #{k2} #{k3} #{k4} #{k5}的可读性太差,为了增强可读性,我们可以将Java程序做如下修改:
CarMapperTest.testInsertCar():
- Map
map = new HashMap<>(); - // 让key的可读性增强
- map.put("carNum", "103");
- map.put("brand", "奔驰E300L");
- map.put("guidePrice", 50.3);
- map.put("produceTime", "2020-10-01");
- map.put("carType", "燃油车");
SQL语句做如下修改,这样可以增强程序的可读性:
CarMapper.xml:
- "1.0" encoding="UTF-8" ?>
- mapper
- PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
- "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
- <mapper namespace="car">
- <insert id="insertCar">
- insert into t_car(car_num,brand,guide_price,produce_time,car_type) values(#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
- insert>
- mapper>
运行程序,查看数据库表:

使用Map集合可以传参,那使用pojo(简单普通的java对象)可以完成传参吗?测试一下:
Car.java:
- package com.dong.mybatis.pojo;
-
- /**
- * POJOs,简单普通的Java对象。封装数据用的。
- * @author captain_dong
- * @version 1.0
- * @since 1.0
- */
- public class Car {
- private Long id;
- private String carNum;
- private String brand;
- private Double guidePrice;
- private String produceTime;
- private String carType;
-
- @Override
- public String toString() {
- return "Car{" +
- "id=" + id +
- ", carNum='" + carNum + '\'' +
- ", brand='" + brand + '\'' +
- ", guidePrice=" + guidePrice +
- ", produceTime='" + produceTime + '\'' +
- ", carType='" + carType + '\'' +
- '}';
- }
-
- public Car() {
- }
-
- public Car(Long id, String carNum, String brand, Double guidePrice, String produceTime, String carType) {
- this.id = id;
- this.carNum = carNum;
- this.brand = brand;
- this.guidePrice = guidePrice;
- this.produceTime = produceTime;
- this.carType = carType;
- }
-
- public Long getId() {
- return id;
- }
-
- public void setId(Long id) {
- this.id = id;
- }
-
- public String getCarNum() {
- return carNum;
- }
-
- public void setCarNum(String carNum) {
- this.carNum = carNum;
- }
-
- public String getBrand() {
- return brand;
- }
-
- public void setBrand(String brand) {
- this.brand = brand;
- }
-
- public Double getGuidePrice() {
- return guidePrice;
- }
-
- public void setGuidePrice(Double guidePrice) {
- this.guidePrice = guidePrice;
- }
-
- public String getProduceTime() {
- return produceTime;
- }
-
- public void setProduceTime(String produceTime) {
- this.produceTime = produceTime;
- }
-
- public String getCarType() {
- return carType;
- }
-
- public void setCarType(String carType) {
- this.carType = carType;
- }
- }
CarMapperTest.testInsertCarByPOJO()
- @Test
- public void testInsertCarByPOJO(){
- // 创建POJO,封装数据
- Car car = new Car();
- car.setCarNum("103");
- car.setBrand("奔驰C200");
- car.setGuidePrice(33.23);
- car.setProduceTime("2020-10-11");
- car.setCarType("燃油车");
- // 获取SqlSession对象
- SqlSession sqlSession = SqlSessionUtil.openSession();
- // 执行SQL,传数据
- int count = sqlSession.insert("insertCarByPOJO", car);
- System.out.println("插入了几条记录" + count);
- }
- <insert id="insertCarByPOJO">
-
- insert into t_car(car_num,brand,guide_price,produce_time,car_type) values(#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
- insert>

#{} 里写的是POJO的属性名,如果写成其他的会有问题吗?
CarMapper.xml:
- <insert id="insertCarByPOJO">
- insert into t_car(car_num,brand,guide_price,produce_time,car_type) values(#{a},#{brand},#{guidePrice},#{produceTime},#{carType})
- insert>
运行程序,出现了以下异常:

错误信息中描述:在Car类中没有找到a属性的getter方法。
修改POJO类Car的代码,只将getCarNum()方法名修改为getA(),其他代码不变,如下:

再运行程序,查看数据库表中数据:

经过测试得出结论:
如果采用map集合传参,#{} 里写的是map集合的key,如果key不存在不会报错,数据库表中会插入NULL。
如果采用POJO传参,#{} 里写的是get方法的方法名去掉get之后将剩下的单词首字母变小写(例如:getAge对应的是#{age},getUserName对应的是#{userName}),如果这样的get方法不存在会报错。
注意:其实传参数的时候有一个属性parameterType,这个属性用来指定传参的数据类型,不过这个属性是可以省略的
CarMapper.xml:
- <insert id="insertCar" parameterType="java.util.Map">
- insert into t_car(car_num,brand,guide_price,produce_time,car_type) values(#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
- insert>
-
- <insert id="insertCarByPOJO" parameterType="com.powernode.mybatis.pojo.Car">
- insert into t_car(car_num,brand,guide_price,produce_time,car_type) values(#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
- insert>