MyBatis

124 阅读33分钟

前言

MyBatis是SSM三大框架的第一个,先了解下框架

  • 框架(Framework),其实就是对通用代码的封装,提前写好了一堆接口和类,我们可以在做项目的时候直接引入这些接口和类(引入框架),基于这些现有的接口和类进行开发,可以大大提高开发效率
  • 框架一般都以jar包的形式存在,jar包中有class文件以及名种配置文件等
  • SSM三大框架的学习顺序:MyBatis, Spring, SpringMVC

MyBatis是持久层框架,用来连接数据库进行增删改查,持久层框架除了它还有:

  • Hibernate
  • jOOQ
  • Guzz
  • Spring Data
  • ActiveJDBC

以前的JDBC代码有什么不足?

  • sql语句写在了Java程序中,想改很麻烦
  • JDBC代码比较繁琐

既然框架大部分都是jar包形式存在,去哪下载MyBatis呢?

  1. 去github下载jar包
  2. 通过maven下载

怎么理解MyBatis的思想?

  • MyBatis本质上就是对JDBC的封装,通过MyBatis完成CRUD

  • ORM (对象关系映射)

    • O (object):JVM中的Java对象
    • R (relational):关系型数据库
    • M (mapping):映射

    这种思想将数据库中的一条记录和java中的一个对象映射起来

  • MyBatis是一个半自动化的ORM,因为框架中的SQL语句要程序员来写,Hibernate就是一个全自动的ORM框架,使用Hibernate的时候,不需要程序员编写SQL语句

  • 创建"和数据库中记录对应对象"的那个类可以叫pojo/bean/domain

MyBatis的特点?

  • 支持定制化SQL,存储过程,基本映射以及高级映射
  • 避免了几乎所有的JDBC代码中手动设置参数以及获取结果集
  • 支持XML开发,也支持注解式开发,(为了保证sql语句的灵活,所以MyBatis大部分是采用XML方式开发)
  • 将接口和Java的POJOs(简单普通的Java对象)映射成数据库中的记录
  • 体积小好学:两个jar包,两个XML配置文件
  • 完全做到sql解耦合
  • 提供了基本映射标签
  • 提供了高级映射标签
  • 提供了XML标签,支持动态SQL的编写等等

现在尝试写第一个MyBatis程序

  1. 准备一张表,注意字段起名要规范,全部小写,单词之间用下划线连接

  2. 用maven在空项目中创建一个web应用的模块,在pom.xml中将mybatis和jdbc驱动导入进来,可以去mvnrepository.com/搜索

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.pan</groupId>
        <artifactId>mybatis-001</artifactId>
        <version>1.0-SNAPSHOT</version>
        <!--打包方式-->
        <packaging>jar</packaging>
    
        <properties>
            <maven.compiler.source>8</maven.compiler.source>
            <maven.compiler.target>8</maven.compiler.target>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        </properties>
    
        <dependencies>
            <!--mybatis依赖-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.10</version>
            </dependency>
    
            <!--mysql驱动依赖-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.30</version>
            </dependency>
        </dependencies>
    </project>
    
  3. 编写MyBatis核心配置文件,最好起名为mybatis-config.xml(约定俗成),放在resources目录下

    注意,除了这个还有多个XxxMapper.xml配置文件(一表对应一个),这个用来编写sql语句,别搞混了,将下面的mybatis-config.xml配置文件里的driver,url...设置成连接本地数据库的参数

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "https://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <!--配置连接数据库的环境-->
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="${driver}"/>
                    <property name="url" value="${url}"/>
                    <property name="username" value="${username}"/>
                    <property name="password" value="${password}"/>
                </dataSource>
            </environment>
        </environments>
        <!--XxxMapper.xml的路径,默认从resources文件夹查找-->
        <mappers>
            <mapper resource="org/mybatis/example/BlogMapper.xml"/>
        </mappers>
    </configuration>
    
  4. 编写XxxMapper.xml文件,它的名字也不是固定的,放在resources文件夹中,注意创建后在上一步的核心配置文件的mapper标签中关联一下XxxMapper.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <!--这里的namespace填的是XxxMapper接口的全类名-->
    <mapper namespace="com.xxx.xxx.xxx">
        <!--mapper标签里可以写增,删,改,查标签-->
    
        <!--id属性是这条sql语句的唯一标识-->
        <insert id="insertCar">
            insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
            values(null,'1003','众泰',0.99,'2001-01-01','核动力')
        </insert>
    
    </mapper>
    
  5. 编写MyBatis程序,(使用MyBatis的类库,编写MyBatis程序,连接数据库,做增删改查就行

    在MyBatis中负责执行SQL语句的对象叫SqlSession,该对象是java程序和DB之间的一次会话

    要想获取SqlSession对象,得先获取SqlSessionFactory对象,通过SqlSessionFactory工厂获取SqlSession对象

    怎么获取SqlSessionFactory对象呢?得先获取SqlSessionFactoryBuilder对象,通过SqlSessionFactoryBuilder的build方法,获取SqlSessionFactory对象

    SqlSessionFactoryBuilder --> SqlSessionFactory --> SqlSession

    package com.pan.mb.test;
    
    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.InputStream;
    
    public class MyBatisTest {
        public static void main(String[] args) throws Exception {
            // 1 获取SqlSessionFactoryBuilder 对象
            // 2 获取SqlSessionFactory 对象
            // 3 获取SqlSession 对象
    
            // 1 获取SqlSessionFactoryBuilder 对象
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            // 2 获取SqlSessionFactory 对象
            InputStream is = Resources.getResourceAsStream("mybatis-config.xml");// mybatis核心配置文件的路径,getResourceAsStream默认从resources路径查找
            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);// 需要传入一个输入流,指向核心配置文件,一般一个数据库一个sqlSessionFactory对象
            // 3 获取SqlSession 对象
            SqlSession sqlSession = sqlSessionFactory.openSession();
            // 执行XxxMapper.xml配置文件中的sql语句
            int count = sqlSession.insert("insertCar");// 这里面传sql语句的id,返回影响数据库的条数
            System.out.println(count);
            // 默认得手动提交
            sqlSession.commit();
        }
    }
    

小细节:

  1. sql语句后面的分号可以加也可以不加
  2. 凡是遇到resource这个单词,大部分情况下加载资源都是从sources目录(类的根路径)下开始加载/查找,这种方式比D://xxx/xxx的方式的可移植性强,因为linux没有盘符
  3. 虽然核心配置文件的位置随意,但是为了可移植性还是要将它放到类的根路径下
  4. XxxMapper.xml文件的路径是固定的吗?也不是

关于MyBatis的事务管理机制

mybatis-config.xml核心配置文件中,有一个<transactionManager type="JDBC"/>标签,type的值有两种可以填,JDBC和MANAGED,其中jdbc是jdbc事务管理器,managed是managed事务管理器,要是选择jdbc事务管理器那么mybatis就会自己管理事务,自己采用原生的jdbc代码去管理事务,就是下面这种情况:

...

SqlSession sqlSession = sqlSessionFactory.openSession();//底层实际调用connection.setAutoCommit(false)

...

sqlSession.commit();//底层实际调用connection.commit()

那假如使用MANAGED事务管理器,则mybatis就不会再负责管理事务了,交给其他容器负责,例如Spring,后期学了spring一般都会交给它管理

对于我们当前的单纯的只有mybatis的情况下,如果配置为,MANAGED,那么事务这块是没人管的,没有人管理事务表示事务压根没有开启

以后注意了,只要你的autoCommit是true,表示没有开启事务,autoCommit是false,表示开启了事务

如何写一个完整版的MyBatis程序?

java

package com.pan.mb.test;

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

public class MyBatisTest2 {
    public static void main(String[] args) {
        SqlSession sqlSession = null;
        try {
            SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
            SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
            sqlSession = sqlSessionFactory.openSession();// 开启会话,底层其实会开启事务
            // 执行sql语句,处理业务
            int count = sqlSession.insert("insertCar");
            // 没有异常,提交事务
            sqlSession.commit();
        } catch (Exception e) {
            // 最好回滚事务
            if (sqlSession != null) {
                sqlSession.rollback();
            }
            e.printStackTrace();
        } finally {
            if (sqlSession != null) {
                sqlSession.close();
            }
        }
    }
}

CarMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--这里的namespace是UserMapper接口的全类名-->
<mapper namespace="asdfadsf">
    <!--mapper标签里可以写增,删,改,查标签-->

    <!--id属性是这条sql语句的唯一标识-->
    <insert id="insertCar">
        insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
        values(null,'1004','奥迪',9.99,'2021-11-01','常规动力')
    </insert>

</mapper>

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://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/mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <!--引入mybatis的映射文件-->
    <mappers>
        <mapper resource="CarMapper.xml"/>
    </mappers>
</configuration>

junit单元测试

将来写的类和方法一多,不可能每个都写一个主方法,这时就需要用到junit单元测试了

直接在pom.xml文件中引入即可

<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
</dependency>

注意,测试类一般和被测试类有相同的目录结构,测试类在test目录下,主程序在main目录下,测试类起名为要测试的类名+Test,一般是一个业务对应一个测试方法,测试方法的规范:起名public void testXxx(){},测试方法的方法名以test开始

package com.pan.mb;

import junit.framework.Assert;
import org.junit.Test;

public class MyBatis02Test {
    @Test
    public void testSayHello() {
        // 实际值
        String actual = new MyBatis02().sayHello();
        // 期望值
        String expected = "hello";
        // 加断言进行测试
        Assert.assertEquals(expected, actual);
    }
}

关于MyBatis的集成日志组件,让调试起来更加方便

在核心配置文件的<configuration>标签中添加<settings>,在里面加上<setting name="logImpl" value="STDOUT_LOGGING"/>开启标准日志,还有别的,如SLF4J,LOG4J,LOG4J2...

<configuration>
	<settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
</configuration>

写一个简单的工具类

SqlSessionUtil工具类

package com.pan.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;

import java.io.IOException;

// MyBatis工具类1.0
public class SqlSessionUtil {

    // 工具类的构造方法一般都私有化的,防止创建对象
    private SqlSessionUtil() {
    }

    private static SqlSessionFactory sqlSessionFactory;

    // 类加载时执行
    static {
        try {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    // 获取会话对象
    public static SqlSession openSession() {
        return sqlSessionFactory.openSession();
    }

}

测试一下

package com.pan.mb;

import com.pan.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

public class TestInsertCar {
    @Test
    public void testInsertCarByUtil() {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        int count = sqlSession.insert("insertCar");
        System.out.println("执行影响了" + count + "条记录");
        sqlSession.commit();
        sqlSession.close();
    }
}

用MyBatis真正做到增删改查

使用MyBatis进行CRUD

通过Map集合传值

上面的例子中,在xml文件中将sql语句写死了

<insert id="insertCar">
    insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
    values(null,'1004','奥迪',9.99,'2021-11-01','常规动力')
</insert>

这时候就可以用 #{} 占位,就像jdbc代码中用?占位一样,写成这样

<insert id="insertCar">
    insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
    values(#{},#{},#{},#{},#{},#{})
</insert>

那写成这样怎么将东西传到#{}里面呢,就可以调用insert方法时不仅传insert标签的id,还要传一个map集合进来,MyBatis会根据占位符中的值去集合中用get方法找该key值对应的value填充上去

package com.pan.mb;

import com.pan.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.HashMap;

public class Test01 {
    @Test
    public void testInsertCar() {
        // 前端传过来数据了,对其进行封装,塞到集合中
        HashMap<String, Object> map = new HashMap<>();
        map.put("key1", "1008");
        map.put("key2", "特斯拉");
        map.put("key3", "88");
        map.put("key4", "2020-02-02");
        map.put("key5", "电动车");
        SqlSession sqlSession = SqlSessionUtil.openSession();
        sqlSession.insert("insertCar", map);
        sqlSession.commit();
        sqlSession.close();
    }
}
<insert id="insertCar">
    insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
    values(null,#{key1},#{key2},#{key3},#{key4},#{key5})
</insert>

这时候就能成功插入了,注意,假如#{}中的key值在集合里不存在,就会填充一个null提交到数据库

但是实际情况中,key1,key2...不会起这种名字,都是数据库中的字段名,即 map.put("carNum", "1008");#{carNum}

通过Java的POJO类传值

pojo类

package com.pan.pojo;

// 封装汽车相关信息的pojo类
public class Car {
    // 数据库表当中的字段应该和p0j0类的属性一一对应
    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 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;
    }

    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;
    }
}

测试以pojo对象的方式插入

@Test
public void testInsertCarByPojo() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    // 封装数据
    Car car = new Car(null, "1006", "奋进者号", 99999.0, "1999-09-09", "液氢液氧");
    sqlSession.insert("insertCar", car);
    sqlSession.commit();
    sqlSession.close();
}

xml

<!--id属性是这条sql语句的唯一标识-->
<insert id="insertCar">
    insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
    values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>

注意,上面的#{carNum}里面的值可不是瞎写的,得在pojo类中有这个属性名(实际上底层调用的是pojo类里的getXxx这个方法),如Pojo对象里的getEmail() ---> #{email} ,说白了mybatis在底层给#{}传值的时候调用pojo对象的get方法先获取值,再想办法传给#{}

xml 只有一个占位符的话可以随便写个aaa,但最好见名知意,

<delete id="deleteById">
    delete from t_car where id = #{id}
</delete>

java

@Test
public void deleteById() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    sqlSession.delete("deleteById", 6);
    sqlSession.commit();
    sqlSession.close();
}

xml

<update id="updateById">
    update t_car set car_num = #{carNum},brand=#{brand},guide_price=#{guidePrice},
    produce_time=#{produceTime},car_type=#{carType} where id = #{id}
</update>

java

@Test
public void updateById() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    Car car = new Car(3L, "1003", "飞飞鱼", 1.0, "2019-05-05", "食物");
    int count = sqlSession.update("updateById", car);
    System.out.println(count);
    sqlSession.commit();
    sqlSession.close();
}

查一个

@Test
public void selectOneById() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    Object car = sqlSession.selectOne("selectOneById", 2);
    System.out.println(car);
    sqlSession.commit();
    sqlSession.close();
}
<select id="selectOneById" resultType="com.pan.pojo.Car">
    select * from t_car where id = #{id}
</select>

注意,xml的select标签得有一个属性,resultType="",里面填一个全限定类名,指定将查到的结果集封装成哪种对象

查是查到了,但是发现封装后的对象的有的属性没有值是null,这是因为pojo类里的属性名和数据库中的字段名字对不上,这时候就得在查询语句中用上AS关键字了

    <select id="selectOneById" resultType="com.pan.pojo.Car">
        select
            id,car_num as carNum,
            brand,
            guide_price as guidePrice,
            produce_time as produceTime,
            car_type as carType
        from
            t_car
        where
            id = #{id}
    </select>

pojo属性名得和数据库字段名对应上

查所有

@Test
public void selectAll() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    List<Car> cars = sqlSession.selectList("selectAll");
    cars.forEach(car -> System.out.println(car));
    sqlSession.commit();
    sqlSession.close();
}
    <select id="selectAll" resultType="com.pan.pojo.Car">
        select
            id,car_num as carNum,
            brand,
            guide_price as guidePrice,
            produce_time as produceTime,
            car_type as carType
        from
            t_car
    </select>

注意:resultType还是指定要封装的结果集的类型,不是指定List类型,是指定List集合中元素的类型 selectList方法:mybatis通过这个方法就可以得知你需要一个List集合,它会自动给你返回一个List集合

关于namespace属性

之前在CarMapper.xml里有个namespace标签,里面的内容是瞎写的asdfadsf

<mapper namespace="asdfadsf">
    <insert id="insertCar">
        insert into t_car(id,car_num,brand,guide_price,produce_time,car_type)
        values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
    </insert>
    <delete id="deleteById">
        delete from t_car where id = #{aaa}
    </delete>
</mapper>

它有啥用呢?

假如有个CarMapper.xml,还有个StudentMapper.xml,里面都有<delete id="deleteById">,怎么区分呢?这时namespace就可以做到防止id冲突,在调用方法时写成asdfadsf.deleteById,即namespace.id这样就知道是哪个xml文件里的语句了

int count = sqlSession.insert("asdfadsf.insertCar");

MyBatis核心配置文件

mybatis.org/mybatis-3/z…

<?xml version="1.0" encoding="UTF-8" ?>
<!--dtd文件用来约束这个文件中只能出现什么标签,并规定标签之间的顺序-->
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">

<!--根标签只能有一个-->
<configuration>
    
    <!--property的name和value属性其实就是键值对,在下面的环境中可以使用${}的方式根据name的值获取value-->
    <properties>
        <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </properties>
    <!--
		上面的方式其实麻烦了,可以写个jdbc.properties放到resources目录下,
		再写个<properties resource="jdbc.properties" />标签导入就行,使用${}访问也可以,用这种好
	-->
    
    
    <!--配置连接数据库的环境,可以有多个环境,一个环境对应一个数据库,一个数据库(环境)对应一个sqlSessionFactory对象-->
    <!--default="xxx"表示当你使用mybatis创建sqlSessionFactory对象的时候,没有指定环境的话,默认使用哪个坏境-->
    <environments default="development1">
        
        <!--其中的一个环境,指定连接的数据库1-->
        <environment id="development1">
            <!--transactionManager用来指定mybatis用什么方式去管理事务-->
            <!--两种值可选:	1.JDBC	2.MANAGED (大小写无所谓)-->
            <!--
				1.JDBC使用原生JDBC代码管理事务
                    conn.setAutoCommit(false);
					...
                    conn.commit()
				2.MANAGED表示mybatis不再负责事务管理,交给其他JEE(JAVAEE)容器来管理,例如Spring
			-->
            <transactionManager type="JDBC"/>
            <!--dataSource被称为数据源-->
            <!--作用是什么?为程序提供Connection对象(但凡是给程序提供Connection对象的都叫做数据源)-->
            <!--数据源实际上是一套规范,JDK中有这套规范:javax.sql.DataSource(这个数据源的规范,这套接口实际上是JDK规定的)-->
            <!--我们自己也可以编写数据源组件,只要实现javax.sql.DataSource接口就行了,实现接口当中所有的方法,这样就有了自己的数据源-->
            <!--如你可以写一个属于自己的数据库连接池(数据库连接池是提供连接对象的,所以数据库连接池就是一个数据源)-->
            <!--常见的数据源组件有哪些呢(常见的数据库连接池有哪些呢)?-->
            <!--阿里巴巴的德鲁伊连接池:druid-->
            <!--c3p0-->
            <!--dbcp-->
            <!--...-->
            <!--type属性用来指定数据源的类型,就是指定具体使用什么方式来获取Connection对象:-->
            <!--type属性有三个值,必须是三选一-->
            <!--type="[UNPOOLED | POOLED | JNDI]"-->
            <!--UNPOOLED:不使用数据库连接池技术,每一次请求过来之后,都是创建新的Connection对象-->
            <!--POOLED:使用mybatis自己实现的数据库连接池-->
            <!--JNDI:可以集成其他第三方的数据库连接池-->
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
                <!--提醒:正常使用连接池的话,池中有很多参数是需要设置的,设置好参数,可以让连接池发挥的更好,事半功倍的效果-->
				<!--具体连接池当中的参数如何配置呢?需要反复的根据当前业务情况进行测试-->
            </dataSource>
        </environment>
        
        <!--另一个环境,指定连接的数据库2-->
        <environment id="development2">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
        
    </environments>
    
    <!--resource属性里写XxxMapper.xml的路径,默认从resources文件夹查找映射文件-->
    <mappers>
        <mapper resource="CarMapper.xml"/>
        <mapper resource="StudentMapper.xml"/>
    </mappers>
    
</configuration>

注意,其中<environments default="development1">里的default属性表示当你使用mybatis创建sqlSessionFactory对象的时候,没有指定环境的话,默认使用哪个坏境,如下所示

package com.pan.mb;

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

public class MyBatisDemo {
    public static void main(String[] args) throws Exception {
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // 1 使用默认环境创建 sqlSessionFactory 对象
        SqlSessionFactory sqlSessionFactory1 = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
        // 2 指定环境创建 sqlSessionFactory 对象
        SqlSessionFactory sqlSessionFactory2 = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"), "development2");

        // 有了不同环境的sqlSessionFactory对象后再执行分别执行sql就可以操作不同的数据库了
    }
}

SqlSessionFactoryBuilder

这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

SqlSessionFactory

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

SqlSession

每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。 下面的示例就是一个确保 SqlSession 关闭的标准模式:

最终版

一般MVC架构模式的项目一般一张表对应一个XxxDao接口文件,还要写一个XxxDaoImpl类去实现这个接口,在XxxDaoImpl实现类里进行数据库操作

SqlSession sqlSession = SqlSessionUtil.openSession();
Account account = sqlSession.selectOne("account.selectAct", actno);
sqlSession.close();

在MyBatis中,假如自己写了接口文件XxxDao,自己又不想去写一个实现类,就可以让MyBatis帮你生成该接口的实现类,但前提是写映射文件时namespace属性不能随便写,sql语句标签中的id属性也不能随便写,namespace里必须写接口的全限定类名,id必须写接口里面的方法名,MyBatis当中实际上采用了代理模式,在内存中生成dao接口的代理类,然后创建代理类的实例

以前有个dao目录专门放dao接口和dao接口的实现类,现在按MyBatis的习惯,一般将dao目录改名为mapper目录,将XxxDao接口文件改名为XxxMapper,都是一回事,只是命名习惯不同了

那么从现在开始映射文件要这样写:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--namespace写成接口的全限定类名-->
<mapper namespace="com.xxx.xxx.mapper.XxxMapper">
    <!--写该接口里面的方法名-->
    <select id="selectXxx" resultType="com.pan.bank.pojo.Xxx">
        select * from xxx where xxx = #{xxx}
    </select>

    <update id="updateXxx">
        update xxx set xxx = #{xxx} where xxx = #{xxx}
    </update>
</mapper>

最终版CRUD

MyBatis核心配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties resource="jdbc.properties"/>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="CarMapper.xml"/>
    </mappers>
</configuration>

jdbc配置文件

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=root

映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.pan.mb.mapper.CarMapper">
    <insert id="insert">
        insert into t_car values (null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
    </insert>
    <delete id="deleteById">
        delete from t_car where id = #{id}
    </delete>
    <update id="update">
        update t_car set car_num=#{carNum},brand=#{brand},guide_price=#{guidePrice},produce_time=#{produceTime},
        car_type=#{carType} where id = #{id}
    </update>
    <select id="selectById" resultType="com.pan.mb.pojo.Car">
        select
            id,
            car_num as carNum,
            brand,
            guide_price as guidePrice,
            produce_time as produceTime,
            car_type as carType
        from t_car where id = #{id}
    </select>
    <select id="selectAll" resultType="com.pan.mb.pojo.Car">
        select
            id,
            car_num as carNum,
            brand,
            guide_price as guidePrice,
            produce_time as produceTime,
            car_type as carType
        from t_car
    </select>
</mapper>

SqlSession工具类

package com.pan.mb.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;

import java.io.IOException;

public class SqlSessionUtil {
    private SqlSessionUtil() {
    }

    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    //全局的,服务器级别的,一个服务器中定义一个即可
    //为什么把SqlSession对象放到ThreadLocal当中呢?为了保证一个线程对应一个SqlSession
    private static ThreadLocal<SqlSession> local = new ThreadLocal<SqlSession>();

    public static SqlSession openSession() {
        SqlSession sqlSession = local.get();
        if (sqlSession == null) {
            sqlSession = sqlSessionFactory.openSession();
            //将sqlSession对象绑定到当前线程
            local.set(sqlSession);
        }
        return sqlSession;
    }

    public static void close(SqlSession sqlSession) {
        if (sqlSession != null) {
            sqlSession.close();
            //注意移除SqlSession对象和当前线程的绑定关系
            //因为Tomcat服务器支持线程池,也就是说用过的线程对象t1,可能下一次还会使用这个t1线程
            local.remove();
        }
    }
}

CarMapper接口

package com.pan.mb.mapper;

import com.pan.mb.pojo.Car;

import java.util.List;

public interface CarMapper {
    // 新增car
    int insert(Car car);

    // 根据id删除car
    int deleteById(Long id);

    // 修改car
    int update(Car car);

    // 根据id查汽车
    Car selectById(Long id);

    // 查所有汽车
    List<Car> selectAll();
}

Car的pojo类

package com.pan.mb.pojo;

// 封装汽车相关信息的pojo类
public class Car {
    // 数据库表当中的字段应该和p0j0类的属性一一对应
    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 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;
    }

    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;
    }
}

测试类

package com.pan.mb.test;

import com.pan.mb.mapper.CarMapper;
import com.pan.mb.pojo.Car;
import com.pan.mb.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class CarMapperTest {
    @Test
    public void testInsert() {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        // 面向接口获取接口的代理对象
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = new Car(null, "1234", "J-20", 999.9, "2014-04-04", "航空煤油");
        mapper.insert(car);
        sqlSession.commit();
    }

    @Test
    public void testDeleteById() {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        int cout = mapper.deleteById(13L);
        System.out.println(cout);
        sqlSession.commit();
    }

    @Test
    public void testUpdate() {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = new Car(10L, "1234", "F-22", 999.9, "1994-04-04", "航空煤油");
        int cout = mapper.update(car);
        System.out.println(cout);
        sqlSession.commit();
    }

    @Test
    public void testSelectById() {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = mapper.selectById(11L);
        System.out.println(car);
    }

    @Test
    public void testSelectAll() {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        List<Car> cars = mapper.selectAll();
        for (Car car : cars) {
            System.out.println(car);
        }
    }
}

可以看到,CarMapper mapper = sqlSession.getMapper(CarMapper.class);现在数据库的操作只需要将以前的XxxDao接口即CarMapper的类对象传给getMapper方法,就会得到一个代理对象,用代理对象直接调用增删改查方法

以前的三层架构的太麻烦了,如下面所示,实现类里调用增删改查方法,现在不需要搞一个实现类了,mybatis帮我们完成

所以要写mybatis的代码,打开三个文件就行了,XxxMapper.java+XxxMapper.xml+测试程序

public class AccountDaoImpl implements AccountDao {
    @Override
    public Account selectByActno(String actno) {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        Account account = sqlSession.selectOne("account.selectAct", actno);
        sqlSession.close();
        return account;
    }
}

小细节

#{}和${}的区别:

  • #():底层使用PreparedStatement,特点:先进行SQL语句的编译,然后给SQL语句的占位符问号?传值,可以避免SQL注入的风险
  • $():底层使用Statement,特点:先进行SQL语句的拼接,然后再对SQL语句进行编译,存在SQL注入的风险

原则:优先使用#{},避免sql注入的风险

那${}有啥用呢?当你需要写类似select * from t_stu order by stu_id asc,这个sql语句中的asc只能用${}传,即先拼接再编译执行

如果需要SQL语句的关键字放到SQL语句中,只能使用${)因为#{}是以值的形式,如'aasdfdf'放到SQL语句当中的

假如有条模糊查询的语句select * from t_car where brand like '%奔驰%',有四种方案拼接

  1. '%${brand}%'
  2. concat('%',#{brand},'%') 这个函数是mysql自带的
  3. concat('%','${brand}','%')
  4. "%"#{brand}"%" 用的最多

别名

可以看到,映射文件里的sql标签包名太长了,很恶心,这样优化一下呢

<!--这玩意不能有别名,只能是全限定类名-->
<mapper namespace="com.pan.mb.mapper.CarMapper">
    <!--com.pan.mb.pojo.Car太长了,可以用别名代替-->
    <select id="selectAll" resultType="com.pan.mb.pojo.Car">
        select id, car_num as carNum from t_car
    </select>
</mapper>

在核心配置文件的<configuration>标签中写这个就可以起别名了

<typeAliases>
    <typeAlias type="com.pan.mb.pojo.Car" alias="car"/>
</typeAliases>

从此,用别名代替

<mapper namespace="com.pan.mb.mapper.CarMapper">
    <!--用别名代替-->
    <select id="selectAll" resultType="car">
        select id, car_num as carNum from t_car
    </select>
</mapper>

注意,别名不区分大小写,而且typeAlias标签的alias属性可以省略,相当于别名就是类的简名"Car"/"cAr"/等等

<typeAliases>
    <typeAlias type="com.pan.mb.pojo.Car"> 相当于别名就是类的简名"Car"/"cAr"/...
</typeAliases>

还有一种更省劲的方法,将这个包下的所有的类全部自动起别名,别名就是类简名

<typeAliases>
    <package name="com.pan.mb.pojo"/>
</typeAliases>

Mappers标签

<mappers>
    <mapper resource="CarMapper.xml"/>
    <mapper resource="StudentMapper.xml"/>要求类的根路径下(resources目录)必须有StudentMapper.xml文件
    <mapper url="file:///d:/xxx/DogMapper.xml" />要求在d:/xxx下有DogMapper.xml文件
    <mapper class="全限定接口名,带有包名"/>如果你采用这种方式,那么你必须保证CarMapper.xml文件和CarMapper接口必须在同一个目录下,且同名
</mappers>

注意,resources目录和java目录看着像两个文件夹,但其实只是idea展示出来像两个文件夹一样,其实都是类的根路径

但其实最终可以写成这样,以后写成这种!

<mappers>    
	<package name="com.pan.mb.mapper"/>
</mappers>

这种方式在实际开发中是使用的,前提是xml文件必须和接口放在一起,并且名字一致,假如mapper文件夹在java目录的com.pan.mb包下,那么就可以在resources文件夹里创建同样的目录结构com/pan/mb/mapper,将该xml文件放进去

模板文件

其实可以将核心配置文件模板,映射文件的模板设置在idea中,这样在创建项目后可以快速生成模板xml文件

设置 --> 编辑器 --> 文件和代码模板 --> 添加

获取自动生成的主键值

数据库的表里的主键一般都是自增的,

添加两个属性,一个表示开启接收自增主键的值,一个表示将值保存的对象的哪个属性中去

<insert id="insert" useGeneratedKeys="true" keyProperty="id">
    insert into t_car values (null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>
@Test
public void testInsert() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    // 面向接口获取接口的代理对象
    CarMapper mapper = sqlSession.getMapper(CarMapper.class);
    Car car = new Car(null, "1234", "J-20", 999.9, "2014-04-04", "航空煤油");
    mapper.insert(car);
    System.out.println(car.getId());// 此时发现得到了主键
    sqlSession.commit();
}

参数问题

简单类型参数

  • byte short int long float double char
  • Byte Short Integer Long Float Double Character
  • String
  • java.util.Date
  • java.sql.Date

下面这两种写法都行,可以指定传过来参数的类型,也可以不写类型让mybatis自己判断,这样效率更高一些

<select id="selectById" resultType="Student" parameterType="java.lang.Long">
    select * from t_stu where id = #{id}
</select>
<select id="selectById" resultType="Student">
    select * from t_stu where id = #{id}
</select>

实际上类型也有别名,如下面这样,具体别的别名参考手册

<select id="selectById" resultType="Student" parameterType="long">
    select * from t_stu where id = #{id}
</select>

也可以告诉mybatis指定传过来的参数在java和数据库中的类型,如#{name,javaType=String,jdbcType=VARCHAR},这样效率更高一些

<select id="selectByName" resultType="Student">
    select * from t_stu where name = #{name,javaType=String,jdbcType=VARCHAR}
</select>

Map参数

创建一个map集合放进去,里面的一个键值对对应一个字段名和值,一个map集合就是一条记录

实体类参数

放一个pojo对象进去

多个参数

使用#{arg0} #{arg1} #{arg2}...接收

也可以用#{param1} {param2} {param3}... 接收

注意,一个下标从0开始,一个从1开始

public interface StudentMapper {
    List<Student> selectByNameAndSex(String name, Character sex);
}
@Test
public void testSelectByNameAndSex() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    List<Student> students = mapper.selectByNameAndSex("李四", '男');
    students.forEach(student -> System.out.println(student));
    sqlSession.close();
}
<select id="selectByNameAndSex" resultType="Student">
    select * from t_stu where name = #{arg0} and sex = #{arg1}
</select>

可以以写成注解的形式,这样就不用写arg0...了

#{param1} {param2} {param3}的形式也能用

public interface StudentMapper {
    List<Student> selectByNameAndSex(@Param("name") String name, @Param("sex") Character sex);
}
@Test
public void testSelectByNameAndSex() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    List<Student> students = mapper.selectByNameAndSex("张三", '男');
    students.forEach(student -> System.out.println(student));
    sqlSession.close();
}
<select id="selectByNameAndSex" resultType="Student">
    select * from t_stu where name = #{name} and sex = #{sex}
</select>

如果执行查询后返回的结果没有合适的pojo对象去接收,就可以将接收类型指定为map,以map的形式接收返回的结果

public interface StudentMapper {
    Map<String, Object> selectByIdReturnMap(Long id);
}
@Test
public void testSelectByIdReturnMap() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    Map<String, Object> map = mapper.selectByIdReturnMap(2L);
    System.out.println(map);
    sqlSession.close();
}
<select id="selectByIdReturnMap" resultType="map">
    select * from t_stu where id = #{id}
</select>

返回多个map集合,放的一个list集合中,也适用于没有合适的pojo对象接收,但返回的结果是多个

不要想复杂了,其实就是一个list里面装了很多map,一个map对应一条记录

public interface StudentMapper {
    List<Map<String, Object>> selectAllReturnManyMapInOneList();
}
@Test
public void testSelectByIdReturnManyMapInOneList() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    List<Map<String, Object>> list = mapper.selectAllReturnManyMapInOneList();
    for (Map<String, Object> map : list) {
        System.out.println(map);
    }
    sqlSession.close();
}
<select id="selectAllReturnManyMapInOneList" resultType="map">
    select * from t_stu
</select>

上面将查到的结果map放到一个list中,想取出id为200的map,只能遍历list,可以改进改进,将查到的结果map放到一个外层的map中,外层的map的key就可以指定为查询结果的id属性,这样想找id为200的map,直接访问外层map的key为200那一项即可

要写一个注解指定大map的key是哪个属性

public interface StudentMapper {
    //返回一个大map集合,大map的key是查询结果小map的id,大map的value是查询结果对应的小map
    @MapKey("id")
    Map<Long, Map<String, Object>> selectAllReturnManyMapInOneMap();
}
@Test
public void testSelectAllReturnManyMapInOneMap() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    Map<Long, Map<String, Object>> map = mapper.selectAllReturnManyMapInOneMap();
    System.out.println(map);
    sqlSession.close();
}
<select id="selectAllReturnManyMapInOneMap" resultType="map">
    select * from t_stu
</select>

结果映射

以前的映射文件里,查询语句里还需要在所查字段后写AS关键字起个别名,将数据库中的stu_num和java的pojo对象中的stuNum对应起来,很麻烦,可以配置如下的

<resultMap id="studentResultMap" type="com.pan.mb.pojo.Student">
    <!--如果数据库表中有主键,一般都是有主键,要不然不符合数据库设计第一范式-->
    <!--如果有主键,建议这里配置一个id标签,注意,这不是必须的,但是官方的解释是什么呢?这样的配置可以让mybatis提高效率-->
    <id property="id" column="id"/>
    <!--property后面填写POJO类的属性名,column后面填写数据库表的字段名-->
    <result property="stuNum" column="stu_num"/>
    <result property="stuName" column="stu_name" javaType="java.lang.String" jdbcType="VARCHAR"/>
    <result property="stuAge" column="stu_age"/>
    <!--如果column和property是一样的,下面这个可以省略-->
    <result property="phone" column="phone"/>
</resultMap>

<!--select标签的resultMap属性,用来指定使用哪个结果映射,resultMap后面的值是上面resultMap的id-->
<select id="selectAllReturnMap" resultMap="studentResultMap">
    select * from t_stu
</select>

除了上面这种,还可以开启驼峰命名法的自动映射,使用这种方式的前提是,属性名遵循java的命名规范,数据库表的列名遵循SQL的命名规范

  • Java命名规范:首字母小写,后面每个单词首字母大写,遵循驼峰命名方式

  • SQL命名规范:全部小写,单词之间采用下划线分割

  • 如carNum <====> car_num

在核心配置文件加一个设置即可开启

<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

动态标签

前端传过来不同的值,要求选择不同的sql语句,这时就会用到动态标签了

if

只有符合test里的条件,值为true,才会拼接里面的sql

<select id="select" resultType="student">
	select * from t_stu where
    <!--1 if标签中test属性是必须的-->
    <!--2 if标签中test属性的值是false,或者true-->
    <!--3 如果test是true,则if标签中的sql语句就会拼接,反之则不会拼接-->
    <!--4 test属性中可以使用的是:-->
    <!--    当使用了@Param注解,那么test中要出现的是@Parami注解指定的参数名,@Param("brand"),那么这里只能使用brand-->
    <!--    当没有使用@Param注解,那么test中要出现的是:param1 param2 param3 arg0 arg1 arg2...-->
    <!--    当使用了POJO,那么test中出现的是POJO类的属性名-->
    <!--5 在mybatis的动态SQL当中,不能使用&&,只能使用and-->
    <if test="name !=null and name != ''">
        name like "%"#{name}"%"
    </if>
    <if test="age !=null and age != 0">
        and age > #{age}
    </if>
</select>

where

where标签的作用:让where子句更加动态智能

  • 所有if条件都为空时,where标签保证不会生成where子句
  • 自动去除某些条件前面多余的and或or
<select id="select" resultType="student">
	select * from t_stu
        <where>
            <if test="name !=null and name != ''">
                and name like "%"#{name}"%"
            </if>
            <if test="age !=null and age != 0">
                and age > #{age}
            </if>
        </where>
</select>

trim

自动添加删除前后缀

<select id="select" resultType="Student">
    select * from t_stu
    <!--prefix:加前缀-->
    <!--suffix:加后缀-->
    <!--prefixOverrides:删除前缀-->
    <!--suffixOverrides:删除后缀-->
    <trim prefix="where" suffixOverrides="and" prefixOverrides="" suffix="">
        <if test="name !=null and name != ''">
            name like "%"#{name}"%" and
        </if>
        <if test="age !=null and age != 0">
            age > #{age}
        </if>
    </trim>
</select>

set

主要使用在update语句当中,用来生成set关键字,同时去掉最后多余的逗号 , 比如我们只更新提交 不为空的字段,如果提交的数据是空null或者"" , 那么这个字段我们将不更新

<update id="update">
    update t_stu
    <set>
        <if test="name !=null and name !=''">name = #{name},</if>
        <if test="age !=null and age !=0">age = #{age},</if>
    </set>
    where id = {#id}
</update>

假如给name字段传了个null,则只会更新age

choose when otherwise

如先根据名字查,没有传名字就根据年龄查

<select id="select" resultType="Student">
    select * from t_stu
    <where>
        <choose>
            <when test="name != null and name !=''">
                name = #{name}
            </when>
            <when test="age !=null and age !=0">
                age = #{age}
            </when>
            <otherwise>
                addr = #{addr}
            </otherwise>
        </choose>
    </where>
</select>

foreach

循环删除

第二种小括号可以不写在外面,在foreach标签属性里指定

<!--foreach标签的属性:-->
<!--collection:指定数组或者集合-->
<!--item:代表数组或集合中的元素-->
<!--separator:循环之间的分隔符-->

<delete id="delete">
    delete from t_stu where id in(
    <foreach collection="array" item="idaaa" separator=",">
        #{idaaa}
    </foreach>
    )
</delete>

<!--也可以在接口里的参数位置自定义写上@Param("ids"),不配注解只能写array或者arg0-->
<delete id="delete">
    delete from t_stu where id in
    <foreach collection="ids" item="idaaa" separator="," open="(" close=")">
        #{idaaa}
    </foreach>
</delete>

<!--以or为分隔符删除也行-->
<delete id="delete">
    delete from t_stu where
    <foreach collection="array" item="id" separator="or">
        id = #{id}
    </foreach>
</delete>

循环插入

<insert id="insert">
    insert into t_stu values
    <foreach collection="stus" item="stu" separator=",">
        (null,#{stu.name},#{stu.age},#{stu.addr},)
    </foreach>
</insert>

sql标签和include标签

sql标签用来声明sql片段 include标签用来将声明的sql片段包含到某个sql语句当中 作用:代码复用,易维护

<sql id="sql001">
    name,age,addr
</sql>

<select id="select" resultType="student">
    select
    <include refid="sql001"/>
    from t_stu
</select>

高级映射

多对一

现在又两张表,学生表和班级表,多个学生对一个班级,学生表有外键

  • 多对一:

    多的一方是:Student 一的一方是:Clazz

  • 怎么分主表和副表 原则:谁在前谁是主表

  • 多对一:多在前,那么多就是主表 一对多:一在前,那么一就是主表

很明显学生表是主表,所以映射在java虚拟机里主对象是student对象,clazz对象是副对象

主对象里有个属性指向了副对象

那么多对一有什么方式实现呢?

  1. 一条SQL语句,级联属性映射
  2. 一条SQL语句,association
  3. 两条SQL语句,分步查询
    • 这种方式常用,优点一是可复用,优点二是支持懒加载

方式一:级联属性映射

学生表有个字段指向班级表,现在进行多表查询

package com.pan.mb.pojo;

// 班级的pojo
public class Clazz {
    private Integer cid;
    private String cname;

    public Clazz() {
    }

    @Override
    public String toString() {
        return "Clazz{" + "cid=" + cid + ", cname='" + cname + '\'' + '}';
    }

    public Integer getCid() {
        return cid;
    }

    public void setCid(Integer cid) {
        this.cid = cid;
    }

    public String getCname() {
        return cname;
    }

    public void setCname(String cname) {
        this.cname = cname;
    }

    public Clazz(Integer cid, String cname) {
        this.cid = cid;
        this.cname = cname;
    }
}
package com.pan.mb.pojo;

// 学生的pojo
public class Student {
    private Integer sid;
    private String name;
    private Clazz clazz;

    @Override
    public String toString() {
        return "Student{" + "sid=" + sid + ", name='" + name + '\'' + ", clazz=" + clazz + '}';
    }

    public Integer getSid() {
        return sid;
    }

    public void setSid(Integer sid) {
        this.sid = sid;
    }

    public String getName() {
        return name;
    }

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

    public Clazz getClazz() {
        return clazz;
    }

    public void setClazz(Clazz clazz) {
        this.clazz = clazz;
    }

    public Student() {
    }

    public Student(Integer sid, String name, Clazz clazz) {
        this.sid = sid;
        this.name = name;
        this.clazz = clazz;
    }
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.pan.mb.mapper.StudentMapper">
    <resultMap id="studentResultMap" type="student">
        <id property="sid" column="sid"/>
        <result property="sname" column="sname"/>
        <result property="clazz.cid" column="cid"/>
        <result property="clazz.cname" column="cname"/>
    </resultMap>
    <select id="selectById" resultMap="studentResultMap">
        select
            s.sid,s.name,c.cid,c.cname
        from
            t_stu s left join t_clazz c on s.cid = c.cid
        where
            s.sid = #{sid}
    </select>
</mapper>

package com.pan.mb.mapper;

import com.pan.mb.pojo.Student;

public interface StudentMapper {
    // 根据id获取学生信息,同时获取关联的班级信息
    // 也可以理解为学生对象中包含班级对象
    Student selectById(Integer id);
}

package com.pan.mb.test;

import com.pan.mb.mapper.StudentMapper;
import com.pan.mb.pojo.Student;
import com.pan.mb.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

public class StudentTest {
    @Test
    public void testSelectById() {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        Student student = mapper.selectById(4);
        System.out.println(student.getSid());
        System.out.println(student.getName());
        System.out.println(student.getClazz().getCid());
        System.out.println(student.getClazz().getCname());
        sqlSession.close();
    }
}

第二种方式:一条SQL语句,association

association翻译为关联,一个student对象关联一个clazz对象

Student selectByIdAssociation(Integer id);
<resultMap id="studentResultMapAssociation" type="student">
    <id property="sid" column="sid"/>
    <result property="sname" column="sname"/>
    <association property="clazz" javaType="Clazz">
        <id property="cid" column="cid"/>
        <result property="name" column="name"/>
    </association>
</resultMap>
<select id="selectByIdAssociation" resultMap="studentResultMapAssociation">
    select
    s.sid,s.name,c.cid,c.cname
    from
    t_stu s left join t_clazz c on s.cid = c.cid
    where
    s.sid = #{sid}
</select>
@Test
public void testSelectByIdAssociation() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    Student student = mapper.selectByIdAssociation(4);
    System.out.println(student.getSid());
    System.out.println(student.getName());
    System.out.println(student.getClazz().getCid());
    System.out.println(student.getClazz().getCname());
    sqlSession.close();
}

第三种方式(这种用的多)

分步查询

两个Mapper接口

package com.pan.mb.mapper;

import com.pan.mb.pojo.Clazz;

public interface ClazzMapper {
        // 分步查询第二步
    Clazz selectByIdStep2(Integer cid);
}
package com.pan.mb.mapper;

import com.pan.mb.pojo.Student;

public interface StudentMapper {
	// 分步查询第一步,先根据学生sid查询学生信息,
    Student selectByIdStep1(Integer id);
}

StudentMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.pan.mb.mapper.StudentMapper">
    <!--两条sql语句完成多对一的分步查询-->
    <!--第一步,根据学生id查询学生所有信息,包含所在班级cid-->
    <resultMap id="studentResultMapByStep" type="Student">
        <id property="sid" column="sid"/>
        <result property="name" column="name"/>
        <!--这里select需要指定另外第二步SQL语句的ID-->
        <association property="clazz" select="com.pan.mb.mapper.ClazzMapper.selectByIdStep2" column="cid"/>
    </resultMap>
    <select id="selectByIdStep1" resultMap="studentResultMapByStep">
        select sid,name,cid from t_stu where sid = #{sid}
    </select>
</mapper>

ClazzMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.pan.mb.mapper.ClazzMapper">
    <!--分步查询第二步,根据cid获取班级信息-->
    <select id="selectByIdStep2" resultType="Clazz">
        select cid,cname from t_clazz where cid = #{cid}
    </select>
</mapper>

测试类

@Test
public void testSelectByIdStep1() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    Student student = mapper.selectByIdStep1(2);
    System.out.println(student);
    System.out.println(student.getSid());
    System.out.println(student.getName());
    System.out.println(student.getClazz().getCid());
    System.out.println(student.getClazz().getCname());
    sqlSession.close();
}

延迟加载

分步查询的优点 第一:复用性增强,可以重复利用,大步拆成多个小碎步,每一个小碎步更加可以重复利用 第二:采用这种分步查询,可以充分利用他们的延迟加载/懒加载机制

什么是延迟加载(懒加载),有什么用? 延迟加载的核心原理是:用的时候再执行查询语句,不用的时候不查询 作用:提高性能,尽可能的不查,或者说尽可能的少查,来提高效率

在mybatis当中怎么开启延迟加载呢? association标签中添加fetchType="lazy" 注意:默认情况下是没有开启延迟加载的,需要设置fetchType="lazy",是局部的设置,只对当前的association关联的sql语句起作用

<resultMap id="studentResultMapAssociation" type="student">
    <id property="sid" column="sid"/>
    <result property="name" column="name"/>
    <association property="clazz" javaType="Clazz" fetchType="lazy">
        <id property="cid" column="cid"/>
        <result property="cname" column="cname"/>
    </association>
</resultMap>

开启全局的延迟加载,在核心配置文件种添加这个标签

<settings>
    <setting name="lazyLoadingEnabled" value="true"/>
</settings>

在实际的开发中,大部分都是需要使用延迟加载的,所以建议开启全部的延迟加载机制

实际开发中的模式是把全局的延迟加载打开,如果某一步不需要使用延迟加载,请设置:fetchType="eager"

一对多

一个班级对应多个学生对象,比如像根据班级表查出某个班的学生都有哪些,这就是一对多

有两种实现方式

  1. collection
  2. 分步查询

第一种方式

这时就要在班级的pojo类里添加一个集合属性

package com.pan.mb.pojo;

import java.util.List;

// 班级的pojo
public class Clazz {
    private Integer cid;
    private String cname;
    private List<Student> stus;

    @Override
    public String toString() {
        return "Clazz{" + "cid=" + cid + ", cname='" + cname + '\'' + ", stus=" + stus + '}';
    }

    public Integer getCid() {
        return cid;
    }

    public void setCid(Integer cid) {
        this.cid = cid;
    }

    public String getCname() {
        return cname;
    }

    public void setCname(String cname) {
        this.cname = cname;
    }

    public List<Student> getStus() {
        return stus;
    }

    public void setStus(List<Student> stus) {
        this.stus = stus;
    }

    public Clazz() {
    }

    public Clazz(Integer cid, String cname, List<Student> stus) {
        this.cid = cid;
        this.cname = cname;
        this.stus = stus;
    }
}

接口类

package com.pan.mb.mapper;

import com.pan.mb.pojo.Clazz;

public interface ClazzMapper {
    // 根据班级编号查询班级信息
    Clazz selectByColloction(Integer cid);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.pan.mb.mapper.ClazzMapper">
    <resultMap id="clazzResultMap" type="Clazz">
        <id property="cid" column="cid"/>
        <result property="cname" column="cname"/>
        <!--一对多,collection是集合的意思-->
        <!--ofType指定集合中元素类型-->
        <collection property="stus" ofType="Student">
            <id property="sid" column="sid"/>
            <result property="name" column="name"/>
        </collection>
    </resultMap>
    <select id="selectByColloction" resultMap="clazzResultMap">
        select c.cid,c.cname,s.sid,s.name from t_clazz c left join t_stu s on c.cid = s.cid where c.cid = #{cid}
    </select>
</mapper>

测试

@Test
public void testSelectByColloction(){
    SqlSession sqlSession = SqlSessionUtil.openSession();
    ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);
    Clazz clazz = mapper.selectByColloction(1000);
    System.out.println(clazz);
    sqlSession.close();
}

第二种:分步查询(最常用)

接口

// 分步查询第一步根据班级编号获取班级信息
Clazz selectByStep1(Integer cid);

映射文件

<!--分步查询第一步,根据班级的cid获取班级信息-->
<resultMap id="clazzResultMapStep" type="Clazz">
    <id property="cid" column="cid"/>
    <result property="cname" column="cname"/>
    <collection property="stus" select="com.pan.mb.mapper.StudentMapper.selectByCidStep2" column="cid"/>
</resultMap>
<!--第一步-->
<select id="selectByStep1" resultMap="clazzResultMapStep">
    select cid,cname from t_clazz where cid=#{cid}
</select>

接口

// 第二步,根据班级编号查询学生信息
List<Student> selectByCidStep2(Integer id);

映射文件

<!--第二步-->
<select id="selectByCidStep2"  resultType="Student">
    select * from t_stu where cid = #{cid}
</select>

测试

@Test
public void testSelectByStep1() {
    SqlSession sqlSession = SqlSessionUtil.openSession();
    ClazzMapper mapper = sqlSession.getMapper(ClazzMapper.class);
    Clazz clazz = mapper.selectByColloction(1000);
    System.out.println(clazz);
    sqlSession.close();
}

优点和上边一样,分步查询复用性增强,可以重复利用,大步拆成多个小碎步,每一个小碎步更加可以重复利用,采用这种分步查询,可以充分利用他们的延迟加载/懒加载机制

多对多

分解成两个一对多

缓存

MyBatis的缓存(cache)机制 执行DQL(select语句)的时候,将查询结果放到缓存当中(内存当中)如果下一次还是执行完全相同的sql语句,直接从缓存中拿数据,不再查数据库了,不再去硬盘上找数据了目的是提高执行效率

缓存机制:使用减少IO的方式提高效率

IO:读文件和写文件

缓存通常是我们程序开发中优化程序的重要手段,你听说过哪些缓存技术呢?

  • 字符串常量池
  • 整数型常量池
  • 线程池
  • 连接池

缓存只针对于DQL语句,也就是说缓存机制只对应select语句

MyBatis缓存包括:

  1. 一级缓存:将查询到的数据存储到SqlSession中
  2. 二级缓存:将查询到的数据存储到SqlSessionFactory中
  3. 或者集成其它第三方的缓存比如EhCache(Java语言开发的),Memcache(C语言开发的)等

一级缓存

一级缓存默认是开启的,不需要做任何配置 原理:只要使用同一个SqlSession对象执行同条SQL语句,就会走缓存

思考:什么时候不走缓存?

  • SqlSession对象不是同一个,肯定不走缓存
  • 查询条件不一样,肯定也不走缓存

思考:什么时候一级缓存失效?

  • 第一次DQL和第二次DQL之间你做了以下两件事中的任意一件,都会让一级缓存清空
    • 执行了sqlSession的clearCache()方法,这是手动清空缓存
    • 执行了INSERT或DELETE或UPDATE语句,不管你是操作哪张表的,都会清空一级缓存

二级缓存

二级缓存的范围是SqlSessionFactory 使用二级缓存需要具备以下几个条件:

  1. <setting name="cacheEnabled" value="true">全局性地开启或关闭所有映射器配置文件中已配置的任何缓存,默认就是true,无需设置
  2. 在需要使用二级缓存的SqlMapper.xml文件中添加配置<cache />标签
  3. 使用二级缓存的实体类对象必须是可序列化的,也就是必须实现观java.io.Serializable接口
  4. SqlSession对象关闭或提交之后,一级缓存中的数据才会被写入到二级缓存当中,此时二级缓存才可用

二级缓存的失效:只要两次查询之间出现了增删改操作,二级缓存就会失效(一级缓存也会失效)

<cache />标签有些属性可以配置

  1. eviction 指定从缓存中移除某个对象的淘汰算法,默认采用LRU策略
    • LRU:Least Recently Used 最近最少使用,优先淘汰在间隔时间内使用频率最低的对象(其实还有一种淘汰算法LFU,最不常用)
    • FIFO:First In First Out 一种先进先出的数据缓存器,先进入二级缓存的对象最先被淘汰
    • SOFT:软引用,淘汰软引用指向的对象,具体算法和JVM的垃圾回收算法有关
    • WEAK:弱引用,淘汰弱引用指向的对象,具体算法和JVM的垃圾回收算法有关
  2. flushInterval 二级缓存的刷新时间间隔,单位毫秒,如果没有设置,就代表不刷新缓存,只要内存足够大,一直会向二级缓存中缓存数据,除非执行了增删改
  3. readOnly
    • true:多条相同的sql语句执行之后返回的对象是共享的同一个,性能好,但是多线程并发可能会存在安全问题
    • false:多条相同的sql语句执行之后返回的对象是副本,调用了clone方法,性能一般但安全
  4. size 设置二级缓存中最多可存储的Java对象数量,默认值1024

集成EhCache

集成EhCache是为了代替mybatis自带的二级缓存,一级缓存是无法替代的

  1. 引入依赖

    <dependency>
        <groupId>org.mybatis.caches</groupId>
        <artifactId>mybatis-ehcache</artifactId>
        <version>1.2.2</version>
        <scope>compile</scope>
    </dependency>
    
  2. 在类的根路径下新建ehcache.xml文件,配置内容可以在网上找

  3. 修改SqlMapper.xml文件中的<cache />标签,添加type属性

    <cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
    

MyBatis逆向工程

所谓的逆向工程是:根据数据库表逆向生成Java的pojo类,SqlMapper.xml文件,以及Mapper接口类等 要完成这个工作,需要借助别人写好的逆向工程插件 思考:使用这个插件的话,需要给这个插件配置哪些信息?

  • pojo类名,包名以及生成位置
  • SqlMapper.xml文件名以及生成位置
  • Mapper接口名以及生成位置
  • 连接数据库的信息
  • 指定哪些表参与逆向工程
  1. 先在maven配置文件中加一个插件

    <!--配置mybatis逆向工程插件-->
    <!--定制构建过程-->
    <build>
        <!--可以配置多个插件-->
        <plugins>
            <!--其中一个插件:mybatis逆向工程插件-->
            <plugin>
                <!--插件的GAV坐标-->
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.4.1</version>
                <!--允许覆盖-->
                <configuration>
                    <overwrite>true</overwrite>
                </configuration>
                <!--插件的依赖-->
                <dependencies>
                    <!--mysql驱动依赖-->
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>8.0.30</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
    

    这样maven的插件里就多了个功能

  2. 在resources目录下创建一个generatorConfig.xml,注意名字只能叫这个

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE generatorConfiguration
            PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
            "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
    <generatorConfiguration>
        <!--
        targetRuntime有两个值:
            MyBatis3Simple:生成的是基础版,只有基本的增删改查
            MyBatis3:生成的是增强版,除了基本的增删改查之外还有复杂的增删改查
        -->
        <context id="DB2Tables" targetRuntime="MyBatis3Simple">
            <!--防止生成重复代码-->
            <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin"/>
    
            <commentGenerator>
                <!--是否去掉生成日期-->
                <property name="suppressDate" value="true"/>
                <!--是否去除注释-->
                <property name="suppressAllComments" value="true"/>
            </commentGenerator>
    
            <!--连接数据库信息-->
            <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                            connectionURL="jdbc:mysql://localhost:3306/mybatis"
                            userId="root"
                            password="root">
            </jdbcConnection>
    
            <!--生成实体类pojo存放位置-->
            <javaModelGenerator targetPackage="com.pan.mb.pojo" targetProject="src/main/java">
                <!--是否开启子包-->
                <property name="enableSubPackages" value="true"/>
                <!--是否去除字段名的前后空白-->
                <property name="trimStrings" value="true"/>
            </javaModelGenerator>
    
            <!--生成SQL映射文件(mapper.xml)存放位置-->
            <sqlMapGenerator targetPackage="com.pan.mb.mapper" targetProject="src/main/resources">
                <!--是否开启子包-->
                <property name="enableSubPackages" value="true"/>
            </sqlMapGenerator>
    
            <!--生成Mapper接口存放位置-->
            <javaClientGenerator
                    type="xmlMAPPER"
                    targetPackage="com.pan.mb.mapper"
                    targetProject="src/main/java">
                <property name="enableSubPackages" value="true"/>
            </javaClientGenerator>
    
            <!--生成对应表及类名-->
            <table tableName="t_car" domainObjectName="Car"/>
        </context>
    </generatorConfiguration>
    

    进行相关配置,指定生成的名字位置

  3. 点击maven的插件即可生成代码

  4. 注意,这只是生成的简单版,想要生成复杂的增强版将<context id="DB2Tables" targetRuntime="MyBatis3Simple">改查<context id="DB2Tables" targetRuntime="MyBatis3">即可

    查所有

    List<Car> cars = mapper.selectByExample(null);// null表示没有条件,即查询所有
    

    按条件查

    @Test
    public void testSelectAll() {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        //QBC风格:Query By Criteria一种查询方式,比较面向对象,看不到SQL语句
        //封装条件:通过CarExample对象来封装查询条件
        CarExample carExample = new CarExample();
        //调用carExample.createCriteria()方法来创建查询条件
        carExample.createCriteria().andCarNumNotBetween("1000", "1100");
        List<Car> cars = mapper.selectByExample(carExample);// null表示没有条件,即查询所有
        for (Car car : cars) {
            System.out.println(car);
        }
        sqlSession.commit();
        sqlSession.close();
    }
    

分页插件PageHelper

实际上每一次在进行分页请求发送的时候,都是要发送两个数据的:

  • 页码pageNum要传送给服务器
  • 每页最示的记录条数pageSize也要传送给服务器
  • 前端提交表单的话,数据格式:uri?pageNum=1&pageSize=10

回顾一下mysql的分页查询

-- 分页查询
SELECT * FROM 表名 LIMIT 开始的索引,取的长度; -- limit后跟两个数字,第一个表示从第几条记录开始取,第二个数字表示取几条记录  这里索引从0开始

SELECT * FROM 表名 LIMIT (pageNum-1)*pageSize ,pageSize

怎么使用插件?

  1. 引入依赖

    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper</artifactId>
        <version>5.3.1</version>
    </dependency>
    
  2. 在mybatis-config.xml里加分页拦截器

    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor"/>
    </plugins>  
    
  3. 在接口里写个方法

    List<Car> selectByPageHelper();
    
  4. 在映射文件里写个查询所有

    <select id="selectByPageHelper" resultType="com.pan.mb.pojo.Car">
        select * from t_car
    </select>
    
  5. 测试,注意要开启

    @Test
    public void testSelectByPageHelper() {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        // 在执行查询语句前,一定要开启分页功能
        int pageNum = 2;
        int pageSize = 3;
        PageHelper.startPage(pageNum,pageSize);
        List<Car> cars = mapper.selectByPageHelper();
        for (Car car : cars) {
            System.out.println(car);
        }
        sqlSession.commit();
        sqlSession.close();
    }
    

其实分页查询得到数据不难,难的是得到页码信息,就像百度下面选页数的地方,要有共多少页等,可以得到PageInfo对象,里面啥信息都有

@Test
    public void testSelectByPageHelper() {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        // 在执行查询语句前,一定要开启分页功能
        int pageNum = 2;
        int pageSize = 3;
        PageHelper.startPage(pageNum,pageSize);
        List<Car> cars = mapper.selectByPageHelper();
        PageInfo<Car> carPageInfo = new PageInfo<>(cars, 6);
        System.out.println(carPageInfo);
        /*
        * PageInfo{pageNum=2, pageSize=3, size=3, startRow=4, endRow=6, total=8, pages=3,
        * list=Page{count=true, pageNum=2, pageSize=3, startRow=3, endRow=6, total=8, pages=3, reasonable=false, pageSizeZero=false}
        * [Car{id=7, carNum='1008', brand='特斯拉', guidePrice=88.00, produceTime='2020-02-02', carType='电动车'},
        * Car{id=8, carNum='1006', brand='奋进者号', guidePrice=99999.00, produceTime='1999-09-09', carType='液氢液氧'},
        * Car{id=10, carNum='1234', brand='F-22', guidePrice=999.90, produceTime='1994-04-04', carType='航空煤油'}],
        * prePage=1, nextPage=3, isFirstPage=false, isLastPage=false, hasPreviousPage=true, hasNextPage=true, navigatePages=6,
        * navigateFirstPage=1, navigateLastPage=3, navigatepageNums=[1, 2, 3]}
        * 
        * 各种信息都有,如上一页下一页等等,调用各种get方法即可
         * */
        sqlSession.commit();
        sqlSession.close();
    }

注解式开发

mybatis中也提供了注解式开发方式,采用注解可以减少sql映射文件(XxxMapper.xml)的配置 当然使用注解式开发的话,sql语句是写在街java程序中的,这种方式也会给sql语句的维护带来成本

使用注解式开发会让代码看着更加简洁,但复杂的语句注解式开发就不行了,因此复杂的sql还是要写在xml文件中

  1. 就不用写XxxMapper.xml文件了,直接在接口文件里写上注解即可,@加增删改查

    @Insert("insert into t_car value(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})")
    int insertByAnnotation(Car car);
    
  2. 测试

    @Test
    public void testInsertByAnnotation() {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        Car car = new Car(null, "12334", "五菱", new BigDecimal(9.9), "1999-01-01", "#93");
        int count = mapper.insertByAnnotation(car);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }
    

查询语句指定字段对应关系

@Select("select * from t_car where id = #{id}")
@Results({
    @Result(property = "id",column = "id"),
    @Result(property = "carNum",column = "car_num")
})
Car selectByAnnotation(Long id);