MyBatis学习笔记

MyBatis学习笔记

mybatis-logo

目录

1. MyBatis简介

1.1 简介

  • MyBatis 是一款优秀的持久层框架,它支持自定义SQL 存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的XML注解来配置和映射原始类型接口和 Java POJO(Plain Old Java Objects,普通老式Java对象)为数据库中的记录。

  • MyBatis本是apache的一个开源项目 iBatis,2010年这个项目由 apache software foundation 迁移到了google code,并且改名为 MyBatis

  • 2013年11月迁移到 Github

1.2 如何获得MyBatis?

  • Maven仓库
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.2</version>
</dependency>

1.3 持久化

数据持久化

  • 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
  • 内存:断电即失
  • 数据库(jdbc),io文件持久化

为什么需要持久化?

  • 有一些对象,不能让他丢失
  • 内存太贵了

1.4 持久层

Dao层,Service层,Controller层。。。

  • 完成持久化工作的代码块
  • 层界限实分明显

1.5 为什么需要MyBatis?

  • 帮助程序员将数据存入到数据库中
  • 方便
  • 传统的 JDBC 代码太复杂了,简化
  • 自动化
  • 不用MyBatis也可以。但MyBatis更容易上手。技术没有高低之分
  • 优点:
    • 简单易学
    • 灵活
    • sql和代码的分离,提高了可维护性
    • 提供映射标签,支持对象与数据库的 orm 字段关系映射
    • 提供对象关系映射标签,支持对象关系组建维护
    • 提供xml标签,支持编写动态 sql
  • 最重要的一点:使用的人多!

2. MyBatis创建工程

2.1 新建Maven工程

  • File --> New --> Project --> Maven --> 直接点Next

    1584968340(D:OtherMarkDownNoteimg%5C1584968340(1).png)
  • 填写GroupId 和 ArtifactId --> 点击Next

    image-20200323211758725
  • 设置项目名 模块名 项目保存地址和maven地址

    image-20200323211523331

2.2 配置工程xml

在项目的pom.xml中写入如下配置

  • 引入依赖
<!--导入依赖-->
<dependencies>
    <!--mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.2.46</version>
    </dependency>
    <!--mybatis-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.2</version>
    </dependency>
    <!--junit-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>
  • 解决maven无法导出或者生效的问题
<!--在builder中配置resources,来防止我们资源导出失败-->
<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
        </resource>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
        </resource>
    </resources>
</build>

2.3 创建数据库和表

在数据库中创建user表并插入测试数据

# 创建user表
DROP TABLE IF EXISTS `user`;
CREATE TABLE IF NOT EXISTS `user`(
	`id` INT(20) PRIMARY KEY auto_increment,
	`name` VARCHAR(30) NOT NULL,
	`password` VARCHAR(30) NOT NULL
);

# 插入数据
INSERT INTO `user` (`name`,`password`)
VALUES
('张三','123'),
('李四','456'),
('王五','admin');

2.4 创建实体类

创建名字跟表名一样的实体类User.java

package com.xp.pojo;

/**
 * User表实体类
 *
 * @author xp
 * @since 2020-03-23    21:42
 */
public class User {
    /**
     * 用户id
     */
    private Integer id;
    /**
     * 用户姓名
     */
    private String name;
    /**
     * 用户密码
     */
    private String password;

    public User() {
    }

    public User(Integer id, String name, String password) {
        this.id = id;
        this.name = name;
        this.password = password;
    }

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

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

2.5 创建表对应的mapper接口

创建名字为UserMapper的User对应的mapper接口,并规定好接口内的方法

package com.xp.mapper;

import com.xp.pojo.User;

import java.util.ArrayList;
/**
 * UserMapper接口
 *
 * @author xp
 * @since 2020-03-23    21:43
 */
public interface UserMapper {
    /**
     * 查找User表中的所有信息
     *
     * @return 存储User表中所有信息的集合
     */
    ArrayList<User> selectUser();
}

2.6 创建映射的sql语句

创建UserMapper.xml文件,并创建namespace中的 sql语句映射

注:在之前版本的 MyBatis 中,命名空间(Namespaces)的作用并不大,是可选的。 但现在,随着命名空间越发重要,你必须指定命名空间。

命名解析为了减少输入量,MyBatis 对所有具有名称的配置元素(包括语句,结果映射,缓存等)使用了如下的命名解析规则。建议使用全限定名

  • 全限定名(比如 “com.xp.mapper.UserMapper”)将被直接用于查找及使用。
  • 短名称(比如 “UserMapper”)如果全局唯一也可以作为一个单独的引用。 如果不唯一,有两个或两个以上的相同名称(比如 “com.xp.mapper.UserMapper”“com.xp.dao.UserMapper”),那么使用时就会产生“短名称不唯一”的错误,这种情况下就必须使用全限定名。
<?xml version="2.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--一个namespace绑定一个mapper-->
<mapper namespace="com.xp.mapper.UserMapper">
    <select id="selectUser" resultType="com.xp.pojo.User">
        select * from USER
    </select>
</mapper>

2.7 配置MyBatis核心配置文件

创建mybatis-config.xml文件,并配置好数据源和mapper

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<!--configuration 核心配置文件-->
<configuration>
    <!--配置数据源-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url"
                          value="jdbc:mysql://localhost:3306/study?useTimezone=true&amp;serverTimezone=GMT%2b8&amp;
                          useUnicode=true&amp;characterEncoding=UTF-8&amp;useSSL=false"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <!--配置mapper映射器-->
    <mappers>
        <mapper resource="UserMapper.xml"/>
    </mappers>
</configuration>

2.8 创建MyBatis工具类

创建MyBatis工具类MyBatisUtil.java

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

/**
 * MyBatis工具类
 *
 * @author xp
 * @since 2020-03-23 22:35
 */
public class MyBatisUtil {

    private static SqlSessionFactory sqlSessionFactory;

    // 防止工具类实例化
    private MyBatisUtil(){}
    
     // 获得SqlSessionFactory
    static {
        String resources = "MyBatis-config.xml";
        try {
            InputStream inputStream = Resources.getResourceAsStream(resources);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取SqlSession对象
     *
     * @return SqlSession对象
     */
    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession();
    }
}

2.9 编写测试类

创建测试类UserMapperTest.java

package com.xp.mapper;

import com.xp.pojo.User;
import com.xp.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;

import org.junit.Test;

import java.util.ArrayList;

public class UserMapperTest {
    @Test
    public void test() {
        // 由于SqlSession不是线程安全的,故使用完后需要关闭资源,官方推荐try这种写法
        try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
            UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
            ArrayList<User> users = userMapper.selectUser();
            users.forEach(System.out::println);
        }
    }
}

2.10 作用域(Scope)和生命周期

流程图

理解我们之前讨论过的不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。

对象生命周期和依赖注入框架

依赖注入框架可以创建线程安全的基于事务的SqlSesson映射器,并将它们直接注入到你的been中,因此可以直接忽略它们的生命周期。

  • SqlSessionFactoryBuilder

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

  • SqlSessionFactory

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

  • SqlSession

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

    try(SqlSession sqlSession = sqlSessionFactory.openSession()){
        // 对应的逻辑代码
    }
    

    在所有代码中都遵循这种使用模式,可以保证所有数据库资源都能正确地关闭

  • 映射器实例

    映射器是一些绑定映射语句的接口。映射器接口的实例是从SqlSession中获得的。虽然从技术层面上来讲,任何映射器实例的最大作用域与它们SqlSession相同。但方法作用域才是映射器实例的最合适的作用域。也就是说,映射器实例应该在调用它们的方法中被获取,使用完毕之后即可丢弃。映射器实例并不需要被显示地关闭。尽管在整个请求作用域保留映射器实例不会有什么问题,但是你很快会发现,在这个作用域上管理太多像SqlSession地资源会让你忙不过来。因此,最好将映射器放在方法作用域内。就像下面的例子一样:

    try(SqlSession sqlSession = SqlSessionFactory.openSession()){
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // 应用逻辑代码
    }
    

3. CRUD

3.1 增加mapper接口中的方法

UserMapper.interface中增加CURD方法

package com.xp.mapper;

import com.xp.model.entity.User;

import java.util.ArrayList;

/**
 * UserMapper 接口
 *
 * @author xp
 * @since 2020-03-23    21:43
 */
public interface UserMapper {
    /**
     * 查找User表中的所有信息
     *
     * @return 存储User表中所有信息的集合
     */
    ArrayList<User> selectUser();

    /**
     * 根据id查找用户信息
     *
     * @param id 用户id
     * @return 查询到的用户信息
     */
    User selectUserById(int id);

    /**
     * 根据id删除用户信息
     *
     * @param id 用户id
     * @return 影响行数
     */
    int deleteUserById(int id);

    /**
     * 修改用户信息
     *
     * @param user 修改后的用户信息
     * @return 影响行数
     */
    int updateUser(User user);

    /**
     * 添加用户信息
     *
     * @param user 添加的用户信息
     * @return 影响行数
     */
    int insertUser(User user);
}

3.2 增加映射的sql语句

UserMapper.xml中增加映射的sql语句

3.2.1 select

<select id="selectUserById" resultType="com.xp.model.entity.User">
    select * from user where id = #{id};
</select>

3.2.2 delete

<delete id="deleteUserById">
    delete from user where id = #{id};
</delete>

3.2.3 update

<update id="updateUser" parameterType="com.xp.model.entity.User">
    update user set name = #{name},password = #{password} where id = #{id};
</update>

3.2.4 insert

<insert id="insertUser">
    insert into user (id,name,password) values (#{id},#{name},#{password});
</insert>

3.3 增加测试方法

UserMapperTest.java中增加对应的CURD测试方法

3.2.1 select

@Test
public void selectUserById() {
    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
		// 执行sql语句        
        User user = mapper.selectUserById(3);
        if (user != null) {
            System.out.println(user);
        } else {
            System.out.println("该用户不存在");
        }
    }
}

3.3.2 delete

@Test
public void deleteUserById() {
    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
		// 执行sql语句
        int row = mapper.deleteUserById(4);
        sqlSession.commit();
        if (row != 0) {
            System.out.println("删除成功!");
        } else {
            System.out.println("删除失败!");
        }
    }
}

3.3.3 update

@Test
public void updateUser() {
    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
		// 执行sql语句
        int row = mapper.updateUser(new User(2, "admin", "123456"));
        sqlSession.commit();
        if (row != 0) {
            System.out.println("修改成功!");
        } else {
            System.out.println("修改失败!");
        }
    }
}

3.3.4 insert

@Test
public void insertUser() {
    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
		// 执行sql语句
        int row = mapper.insertUser(new User(null, "游医", "12345"));
        sqlSession.commit();
        if (row != 0) {
            System.out.println("添加成功!");
        } else {
            System.out.println("添加失败!");

        }
    }
}

3.4 CURD注意点

  • 增删改需要提交事务

    sqlSession.commit();
    
  • 标签不要匹配错

  • resource绑定mapper,需要使用路径

    <mapper resource="mapper/UserMapper.xml"/>
    
  • 程序配置文件必须符合规范

    配置文件中不能多了空格 双引号之类不符合规范的字符
    
  • NullPointerException,没有注册到资源

  • maven没有导出问题

    <!--在builder中配置resources,来防止我们资源导出失败-->
    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
            </resource>
        </resources>
    </build>
    

3.5 万能Map

3.5.1 规定mapper接口中的方法

UserMappe接口中增加如下方法

/**
 * 根据id查找用户信息
 *
 * @param map 参数map
 * @return 查询到的用户信息
 */
User selectUserById2(Map<String,Object> map);

/**
 * 根据id删除用户信息
 *
 * @param map 参数map
 * @return 影响行数
 */
int deleteUserById2(Map<String,Object> map);

/**
 * 修改用户信息
 *
 * @param map 参数map
 * @return 影响行数
 */
int updateUser2(Map<String,Object> map);

/**
 * 添加用户信息
 *
 * @param map 参数map
 * @return 影响行数
 */
int insertUser2(Map<String,Object> map);

3.5.2 创建映射的sql语句

UserMapper.xml文件中增加如下映射的sql语句

<!--万能map-->
<select id="selectUserById2" resultType="com.xp.model.entity.User">
    select * from user where id = #{id};
</select>

<delete id="deleteUserById2">
    delete from user where id = #{deleteId};
</delete>

<update id="updateUser2">
    update user set name = #{name},password=#{password} where id = #{id};
</update>

<insert id="insertUser2">
    insert into user (id,name,password) values (#{id}, #{name}, #{password});
</insert>

3.5.3 编写测试类

UserMapperTest中加入如下测试方法

@Test
public void selectUserById2() {
    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // 创建个存储参数的map,map的key必须和mapper的配置文件中的#{xxx}相同
        Map<String, Object> map = new HashMap<>();
        // 将参数放入map中
        map.put("id", "3");
        System.out.println(mapper.selectUserById2(map));
    }
}

@Test
public void deleteUserById2() {
    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Map<String, Object> map = new HashMap<>();
        map.put("deleteId", "4");

        int row = mapper.deleteUserById2(map);
        sqlSession.commit();
        if (row != 0) {
            System.out.println("删除成功");
        } else {
            System.out.println("删除失败");
        }
    }
}

@Test
public void updateUser2() {
    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        Map<String, Object> map = new HashMap<>();
        map.put("id", "2");
        map.put("name", "李四");
        map.put("password", "321");

        int row = mapper.updateUser2(map);
        sqlSession.commit();
        if (row != 0) {
            System.out.println("修改成功");
        } else {
            System.out.println("修改失败");
        }
    }
}

@Test
public void insertUser2() {
    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        HashMap<String, Object> map = new HashMap<>();
        map.put("id", 4);
        map.put("name", "ben");
        map.put("password", "123");

        int row = mapper.insertUser2(map);
        sqlSession.commit();
        if (row != 0) {
            System.out.println("添加成功");
        } else {
            System.out.println("添加失败");
        }
    }
}

3.5.4 万能Map的注意点

  • Map传递函数,直接在sql中取出key即可!

    parameterType = "map"
    
  • 对象传递参数,直接在sql中取对象的属性即可!

    parameterType = "Object"
    
  • 只有一个基本类型参数的情况下,可以直接在sql中取到

  • 多个参数用map或者注解!

3.6 模糊查询

  • java代码执行的时候,传递通配符% %

    List<User> list = mapper.getUserListByName("%"+keywoed+"%");
    
  • sql拼接中使用通配符

    select * from user where name like concat('%',#{keyword},'%');
    

4.配置解析

4.1 核心配置文件

  • mybatis-config.xml
  • 核心配置文件的配置结构的顶层结构

image-20200325150932517

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

4.2 环境配置(environments)

MyBatis 可以配置成适应多种环境

不过要记住:尽管可以配置多个环境,但每个SqlSessionFactory实例只能选择一种环

MyBatis 默认的事务管理就是JDBC,连接池:POOLED

4.2.1 事务管理器(transactionManager

MyBatis中有两种类型的事务管理器(也就是type="[JDBC|MANAGED]"):

  • JDBC:这个配置直接使用了JDBC的提交和回滚设施,它依赖从数据源获得的连接来管理事务的作用域

  • MAVAGED:这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如JEE应用服务器的上下文)。默认情况下,它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将closeConnection属性设置为false来阻止默认的关闭行为。例如:

    <transactionManager type="MANAGED">
      <property name="closeConnection" value="false"/>
    </transactionManager>
    

如果正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。

4.2.2 数据源(dataSource)

dataSource元素使用标准的JDBC数据源接口来配置JDBC连接对象的资源。

  • 大多数MyBatis应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。

有三种内建的数据源类型(也就是type="[UNPOOLED|POOLED|JNDI]"):

  1. UNPOOLED

    这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。UNPOOLED类型的数据源仅仅需要配置以下五种属性:

    • driver:这是JDBC驱动的Java类全限定名(并不是JDBC驱动中可能包含的数据源类)。
    • url:这是数据库的JDBC URL地址。
    • username:登录数据库的用户名。
    • password:登录数据库的密码。
    • defaultTransactionIsolationLevel:默认的连接事务隔离级别。
    • defaultNetworkTimeout:等待数据库操作完成的默认网络超时时间(单位:毫秒)。查看java.sql.Connection#setNetworkTimeout()的API文档以获得更多信息。

    作为可选项,也可以传递属性给数据库驱动。只需在属性名上"driver."前缀即可,例如

    • driver.encoding=UTF8

    这将通过DriverManager.getConnection(url,driverProperties)方法传递值为UTF8encoding属性给数据库驱动

  2. POOLED

    这种数据源的实现利用”池“的概念将JDBC连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。这种处理方式很流行,能使并发Web应用快速响应请求。

    出了上述UNPOOLED下的属性外,还有更多属性用来配置POOLED的数据源:

    • poolMaximumActiveConnections:在任意时间可存在的活动(正在使用)连接数量,默认值:10。
    • poolMaximumIdleConnections:在任意时间可能存在的空闲连接数。
    • poolMaximumCheckoutTime:在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000毫秒(即20秒)
    • poolTimeToWait:这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值:20000毫秒(即20秒)
    • poolMaximumLocalBadConnectionTolerance:这是一个关于坏连接容忍度的底层设置,作用于每一个尝试从缓存池获取连接的线程。如果这个线程获取到的是一个坏连接,那么这个数据源允许这个线程尝试重新获取一个新的连接,但是这个重新尝试的次数不应该超过poolMaxmumIdleConnectionspoolMaximumLocalBadConnectionTolerance之和。默认值为3(MyBatis 3.4.5后的版本)。
    • poolPingQuery:发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。默认是"NO PING QUERY SET",这会导致多数数据库驱动出错时返回恰当的错误信息。
    • poolPingEnabled:是否启用侦测查询。若开启,需要设置poolPingQuery属性为一个可执行的SQL语句(最好是一个速度非常快的SQL语句),默认值:false
    • poolPingConnectionsNotUsedFor:配置poolPingQuery的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测——当然仅当poolPingEnabledtrue时适用)
  3. JNDI

    这个数据源实现时为了能在EJB或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个JNDI上下文的数据源引用。这种数据源配置只需要两个属性:

    • initial_context:这个属性用来在InitialContext中寻找上下文(即,initialContext.lookup(initial_context))。这个是可选属性,如果忽略,呢么将会直接从InitialContext中寻找data——source属性。

    • data_soutrce:这是引用数据源实例位置的上下文路径。提供了initial_context配置时会在其返回的上下文中进行查找,没有提供时则直接在InitialContext中查找。

    和其他数据源配置类似,可以通过添加前缀"env."直接把属性传递给InitialContext。比如:

    • env.encoding=UTF8

    这就会在InitialContext实例化时往它的构造方法传递值为UTF8encoding属性。

4.3 属性(properties)

我们可以通过properties属性来实现引用配置文件

这些属性都是可外部配置且可动态替换的,既可以在典型的java属性文件中配置,亦可通过properties元素的子元素来传递。【pro.properties】

4.3.1 创建properties配置文件

在resources中创建pro.properties文件

drive=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/study?useTimezone=true&serverTimezone=GMT%2b8&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username=root
password=root

4.3.2 修改核心配置文件

  1. 添加properties标签,并设置resource。resource为pro.propertise的路径

  2. 修改dataSource标签内的property子标签内的value

  3. 引用properties中的子元素来传递的格式为 $

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<!--configuration 核心配置文件-->
<configuration>
    <properties resource="pro.properties"/>
    <!--配置数据源-->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${drive}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

也可以在properties中直接写property,并设置name和value

<properties resource="pro.properties">
    <property name="username" value="root"></property>
    <property name="password" value="root"></property>
</properties>
  • 可以直接引入外部文件
  • 可以在其中增加一些属性配置
  • 如果两个文件有同一个字段,优先使用外部配置文件的!

4.3.3 注意事项

核心配置文件mybatis-config.xml中标签结构是有顺序的,如果不按顺序编写标签,则会报以下错误

image-20200325174153865

4.4 类型别名(typeAliases)

  • 类型别名是 Java类型设置一个短的名字
  • 存在的意义是降低冗余的全限定类名书写
  1. 单独给类取别名
<!--别名-->
<typeAliases>
    <!--单独给类取别名-->
    <typeAlias type="com.xp.model.entity.User" alias="user"/>
</typeAliases>
  1. 给包内的每个类自动生成别名

    指定一个包名,MyBatis 会在包名下面搜索需要的Java Bean

<!--别名-->
<typeAliases>
    <!--给包内的每个类自动生成别名-->
    <package name="com.xp.model.entity"/>
</typeAliases>

​ 如上,每一个包com.xp.model.entity中的Java Bean,在没有注解的情况下,会使用Bean的首字母小写的非限定类名来作为它的别名。比如com.xp.model.entity.User的别名为user(虽然直接用类名User作为别名也能达到效果,但官网明确写的是首字母小写的非限定类名来作为它的别名);若有注解,则别名为其注解值。

<!--应用别名-->
<select id="selectUser" resultType="user">
    select * from user;
</select>
  1. 使用注解取别名

    在类的上面加上注解@Alias并写上取的别名

@Alias("alias")
public class User {
	// Java 代码
}

​ 在mapper中应用别名

<!--应用别名-->
<select id="selectUser" resultType="alias">
    select * from user;
</select>

类型别名使用总结:

在实体类比较少的时候,使用第一种方式。

如果实体类十分多,建议使用第二种。

第一种可以DIY别名,第二种则不行。如果非要改,则用第三种,在实体类上增加注解。

下面是一些常见的Java类型内建的类型别名。它们都是不区分大小写的,注意,为了应对原始类型的命名重复,采取了特殊的命名风格

别名 映射的类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal Decimal
bigDecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arrayList ArrayList
collection Collection
Iterator Iterator

4.5 设置(Setting)

这是MyBatis中极为重要的调整位置,它们会改变MyBatis的运行时行为。

日志实现

image-20200405112337066

开启缓存和懒加载

image-20200405113137094

4.6 其他设置

  • 类型处理器(typeHandlers)
  • 对象工厂(objectFactory)
  • 插件(plugins)
    • mybatis-generator-core
    • mybatis-plus
    • 通用mapper

4.7 映射器(mappers)

MapperRegistry:注册绑定Mapper文件:

方式一:通过resource注册绑定mapper(推荐使用)

<!--配置mapper映射器-->
<mappers>
    <mapper resource="mapper/UserMapper.xml"/>
</mappers>

方式二:使用class文件注册绑定Mapper

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

方式三:使用扫描包注册绑定Mapper

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

注意点:

方式二和方式三

  • 接口和他的Mapper配置文件必须同名
  • 接口和他的Mapper配置文件必须在同一个包下

5. 解决属性名和字段名不一致的问题

数据库中字段名

image-20200405191228289

实体类是

public class User {
    /**
     * 用户id
     */
    private Integer id;
    /**
     * 用户姓名
     */
    private String name;
    /**
     * 用户密码
     */
    private String pwd;
}

查询后结果

image-20200405191758863

可以明显地看出,查询出后来放入实体类的password为null,也就是说并没有查询到对应的字段。

解决方法:

  • 起别名
<select id="selectUser" resultType="user">
    select id,name,password pwd from user;
</select>
  • 使用resultMap映射
<select id="selectUser" resultMap="userMap">
    select * from user;
</select>

<resultMap id="userMap" type="user">
    <result property="id" column="id"/>
    <result property="name" column="name"/>
    <result property="pwd" column="password"/>
</resultMap>

也可只映射不同名的的字段

<resultMap id="userMap" type="user">
    <result property="pwd" column="password"/>
</resultMap>

ResultMap

  • ResultMap元素是MyBatis中最重要最强大的元素。
  • ResultMap的设计思想是,对于简单的语句分本不需要配置显式的映射,而对于复杂一点的语句,只需要描述它们的关系就行了。
  • ResultMap最优秀的地方在于,虽然你已经对它相当了解了,但根本就不需要显示地用到它们。
  • 如果世界总是这么简单就好了。

6. 日志

如果一个数据库操作,出现了异常,我们需要排错,日志就是最好的帮手!

image-20200405112337066

  • SLF4J
  • LOG4J 【掌握】
  • LOG4J2
  • JDK_LOGGING
  • COMMONS_LOGGING
  • STDOUT_LOGGING 【掌握】(是MyBatis自带的日志)
  • NO_LOGGING

在MyBatis种具体使用哪一个日志实现,在设置中修改value。

  • STDOUT_LOGGING 配置
<!--设置-->
<settings>
    <!--设置日志实现-->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
  • LOG4J 配置
  1. 配置xml
<!--设置-->
<settings>
    <!--设置日志实现-->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
  1. 导入jar包
<!--导入依赖-->
<dependencies>
    <!--log4j-->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.12</version>
    </dependency>
</dependencies>
  1. 创建log4j.properties文件,并放在resources目录下
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file

#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=【%c】-%m%n

#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/xp.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=【%p】【%d{yy-MM-dd}】【%c】%m%n

#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
  1. 测试log4j是否配置完成

image-20200406224432642

可能的报错:

log4j:WARN No appenders could be found for logger (org.apache.ibatis.logging.LogFactory).
log4j:WARN Please initialize the log4j system properly.

这个报错的意思就是没有找到log4j配置文件

解决方法:

  • 检查log4j.properties文件名是否正确,完整文件名必须是log4j.properties
  • log4j.properties文件放在resource目录下
  • 检查log4j.properties文件中的log4j.rootLogger是否配置
  1. log4j的简单使用
  • 在要使用Log4j的类中导入包import org.apache.log4j.Logger

  • 创建日志对象,参数为需要打印日志的类

private static Logger logger = Logger.getLogger(UserMapperTest.class);
  • 日志常用的三种级别:info,debug,error
@Test
public void log4jTest() {
    logger.info("info:进入log4jTest");
    logger.debug("debug:进入log4jTest");
    logger.error("error:进入log4jTest");
}

控制台打印如下:

image-20200407113405350

日志文件xp.log内容如下:

image-20200407115025223

7. 分页

思考:为什么要分页?

  • 减少数据的处理量

使用limit分页

# select 查询的字段名 from 表明 [其他限制条件如 where.in等] limit 起始的下标,查询的个数
select * from user limit 0,3;

7.1 使用MyBatis实现分页

  1. 编写接口
/**
 * 分页查询
 * @return  分页查询到的结果
 */
List<User> getUserByLimit(Map<String,Integer> map);
  1. 在Mapper配置文件中编写SQL语句
<!--分页查询-->
<select id="getUserByLimit" parameterType="map" resultMap="userMap">
    select * from user limit #{startIndex},#{endIndex};
</select>
  1. 测试
@Test
public void getUserByLimit() {
    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // 将要传入SQL语句的内容放入map中
        Map<String, Integer> map = new HashMap<>();
        map.put("startIndex",0);
        map.put("endIndex",2);
        // 执行SQL语句
        List<User> userByLimit = mapper.getUserByLimit(map);
        userByLimit.forEach(System.out::println);
    }
}

7.2 RowBounds分页(了解)

  1. 接口
/**
 * 使用RowBounds实现分页查询
 * @return  分页查询到的结果
 */
List<User> getUserByRowBounds();
  1. 在mapper配置文件中增加SQL语句
<!--使用RowBounds实现分页-->
<select id="getUserByRowBounds" resultMap="userMap">
select * from user;
</select>
  1. 测试
@Test
public void getUserByRowBounds(){
    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()){
        // 使用RowBounds的构造函数传入limit参数
        RowBounds rowBounds = new RowBounds(1,3);
        // 执行SQL语句
        List<Object> userList = sqlSession.selectList("com.xp.mapper.UserMapper.getUserByRowBounds",null,rowBounds);
        userList.forEach(System.out::println);
    }
}

8. 使用注解开发

8.1 面向接口编程

  • 大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程。
  • 根本原因:解耦,可拓展,提高服用,分层开发中,上层不用管具体实现,大家都遵守共同的标准,使得开发变得容易,规范性更好。
  • 在一个面向对象的系统中,系统的各种功能是由许许多多的的不同对象协作完成的,在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲,就不那么重要了。
  • 而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

关于接口的理解

  • 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)分离。
  • 接口的本身反映了系统设计人员对系统的抽象理解。
  • 接口应有两类:
    • 第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);
    • 第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface);
  • 一个个体有可能由多个抽象面。抽象体与抽象面是有区别的。

三个面向区别

  • 面向对象是指:我们在考虑问题时,以对象为单位,考虑它的属性及方法。
  • 面向过程是指:我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现
  • 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题。更多的体现就是对系统整体的架构。

8.2 使用注解开发

  1. 注解在接口上实现
  • 当没有参数时
/**
 * 根据注解查询
 * @return  查询的结果
 */
@Select("select * from user;")
List<User> getUserByAnnotation();
  • 参数个数为一个的时候

    关于@param注解

    • 基本类型的参数或者String类型,需要加上
    • 引用类型不需要加
    • 如果只有一个基本类型的话,可以忽略,但是建议大家都加上!
    • 我们在SQL中引用的就是我们这里的@param()中设定的属性名!
/**
 * 根据注解查询(单个参数)
 * @return  查询的结果
 */
@Select("select * from user where id = #{uid};")
User getUserByIdAndAnnotation(@Param("uid") Integer id);
  • 多个参数时
/**
 * 根据注解查询(多个参数)
 * @return  查询的结果
 */
@Select("select * from user where id = #{uid} and name = #{name};")
User getUserByIdAndNameAndAnnotation(@Param("uid") Integer id,@Param("name") String name);
  1. 在核心配置文件中绑定接口
<!--配置mapper映射器-->
<mappers>
    <!--绑定接口-->
    <mapper class="com.xp.mapper.UserMapper"/>
</mappers>
  1. 测试
@Test
public void getUserByAnnotation() {
    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()) {
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        List<User> userList = mapper.getUserByAnnotation();
        userList.forEach(System.out::println);
    }
}

本质:反射机制实现

底层:动态代理

MyBatis详细执行流程:

image-20200407161428341

8.3 CRUD

/**
 * 根据注解增加用户
 *
 * @param user 增加的用户
 * @return 影响行数
 */
@Insert("insert into user (id,name,password) values (#{id},#{name},#{password});")
int insetUserByAnnotation(User user);

/**
 * 根据id和注解增加用户
 *
 * @param id id
 * @return 影响行数
 */
@Delete("delete from user where id = #{id};")
int deleteUserByIdAndAnnotation(@Param("id") Integer id);

/**
 * 根据注释修改用户信息
 *
 * @param user 修改后的用户信息
 * @return 影响行数
 */
@Update("update user set id = #{id}, name = #{name}, password = #{password} where id = #{id};")
int updateUserByAnnotation(User user);
/**
 * 根据id查询用户信息
 * @return  查询的结果
 */
@Select("select * from user where id = #{id};")
User getUserByIdAndAnnotation(@Param("id") Integer id);

9. Lombok

Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.
Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.

Lombok 项目是一个java库,它可以自动插入编辑器并构建工具,为java增色。

永远不用再编写另一个getter或equals方法,使用一个注释,您的类就有了一个功能齐全的构建器,自动化您的日志变量,等等。

9.1 Lombok实现原理

自从Java 6起,javac就支持“JSR 269 Pluggable Annotation Processing API”规范,只要程序实现了该API,就能在javac运行的时候得到调用。

Lombok就是一个实现了"JSR 269 API"的程序。在使用javac的过程中,它产生作用的具体流程如下:

  1. javac对源代码进行分析,生成一棵抽象语法树(AST)

  2. javac编译过程中调用实现了JSR 269的Lombok程序

  3. 此时Lombok就对第一步骤得到的AST进行处理,找到Lombok注解所在类对应的语法树 (AST),然后修改该语法树(AST),增加Lombok注解定义的相应树节点

  4. javac使用修改后的抽象语法树(AST)生成字节码文件

9.2 使用步骤

  1. 在IDEA中安装Lombok插件

    File ->Settings -> plugins -> 搜索插件 -> 安装插件

image-20200408114600034

若是搜索不到显示search results are not loaded check the internet connection

解决方案:

  • 检查电脑防火墙是否关闭,若没关闭,则将防火墙关闭

image-20200408115148492

  • File -> Settings -> Appearance & Behavior -> System Settings ->HTTP Proxy -> 勾选Auto-detect proxy settings

image-20200408115305483

  • File -> Settings -> Appearance & Behavior -> System Settings -> Updates -> 将User secure connection 勾选去掉

image-20200408115510070

  • 重启idea,然后重新进入plugins 搜索插件并下载

若重启idea后还是不能搜索到插件,则重启多几次,若还不行,则电脑关机后等待5分钟后重启电脑去安装插件。

若都未解决,网上有人说改用开手机热点来下载插件或离线安装插件,可以尝试一下。

  1. 导入Lombok的jar包
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<!-- Lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.10</version>
</dependency>
  1. 在需要使用lombok插件的实体类上加上注解
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    /**
     * 用户id
     */
    private Integer id;
    /**
     * 用户姓名
     */
    private String name;
    /**
     * 用户密码
     */
    private String pwd;
}

9.3 常用注解

注解 使用
@Getter 作用在类上,生成所有类成员变量的getter方法;作用在成员变量上,生成该成员变量的getter方法。可以设定访问权限及是否懒加载等。
@Setter 作用在类上,生成所有类成员变量的setter方法;作用在成员变量上,生成该成员变量的setter方法。可以设定访问权限及是否懒加载等。
@ToString 作用于类上,覆盖默认的toString方法,可以通过of属性限定显示某些字段,通过exclude属性排除某些字段。
@EqualsAndHashCode 作用于类上,覆盖默认的equals和hashCode方法。
@NonNull 主要作用于成员变量和参数中,标识不能为空,否则抛出空指针异常。
@NoArgsConstructor 作用于类上,用于生成该类的无参构造器。
@AllArgsConstructor 作用域类上,用于生成该类全参构造器。
@RequiredArgsConstructor 作用于类上,生成final和@NonNull注解的成员变量的构造器。
@Data 作用于类上,相当于同时使用@Getter @Setter @ToString @EqualsAndHashCode @RequiredArgsConstructor注解。
@Builder 作用于类上,将该类变成建造者模式。
@Log 作用于类上,生成日志变量。针对不同的日志实现产品,有不同的注解。

10. 多对一处理

假设场景:

多个学生对应一个老师

  1. 创建数据库表,并准备数据
# 创建教师表
create table if not exists teacher(
    id int(5) primary key ,
    name varchar(30) not null
);

# 创建学生表,tid字段为外键
create table if not exists student (
    id int(5) primary key ,
    name varchar(30) not null,
    tid int(5),
    constraint teacher_id_FK foreign key (tid) REFERENCES teacher(id)
);

# 准备数据
insert into teacher (id,name) values (1,'王老师');
insert into student (id,name,tid) values
(1,'张三',1),
(2,'李四',1),
(3,'王五',1),
(4,'赵六',1),
(5,'钱七',1);
  1. 创建实体类

    创建教师实体类

/**
 * 教师实体类
 *
 * @author xp
 * @since 2020-04-08 14:09
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {

    /**
     * 教师id
     */
    private Integer id;

    /**
     * 教师姓名
     */
    private String name;
}

​ 创建学生实体类

/**
 * 学生实体类
 *
 * @author xp
 * @since 2020-04-08 14:13
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    /**
     * 学生id
     */
    private Integer id;

    /**
     * 学生姓名
     */
    private String name;

    /**
     * 教师id
     */
    private Integer tid;
}
  1. 创建mapper接口
public interface StudentMapper {
    List<Student> getStudent();
}
  1. 配置映射器
  • 联表查询(推荐)
  • 子查询

association:对对象进行映射,配合javaType使用

<!-- 使用联表查询配置映射器 -->
<!-- 按照结果嵌套查询 -->
<mapper namespace="com.xp.mapper.StudentMapper">
    <select id="getStudent" resultMap="studentMap">
        select s.id sid, s.name sname, t.id tid, t.name tname
        from student s
        inner join teacher t
        on s.tid = t.id;
    </select>
    <!-- 复杂的查询结果需要使用resultMao进行映射 -->
    <!-- association:对对象进行映射,配合javaType使用 -->
    <resultMap id="studentMap" type="Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <association property="teacher" javaType="Teacher">
            <result property="id" column="tid"/>
            <result property="name" column="tname"/>
        </association>
    </resultMap>
</mapper>

<!-- 使用子查询配置映射器 -->
<!-- 查询 student 表信息 -->
<select id="getStudent" resultMap="studentMap">
    select * from student;
</select>
<!-- 绑定子查询 -->
<resultMap id="studentMap" type="Student">
    <result column="id" property="id"/>
    <result column="name" property="name"/>
    <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<!-- 子查询 -->
<select id="getTeacher" resultType="teacher">
    select * from teacher where id = #{id};
</select>
  1. 测试
@Test
public void getStudent(){
    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()){
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);

        List<Student> students = mapper.getStudent();
        students.forEach(System.out::println);
    }
}

Select 元素的属性

属性 描述
id 在命名空间种唯一的标识符,可以被用来引用这条语句
parameterType 将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为MyBatis 可以通过类型处理器(TypeHandler) 推断出具体传入语句的参数,默认值为未设置(unset)。
parameterMap 用于引用外部parameterMap 的属性,目前已被废弃。请使用行内参数映射和parameterType 属性。
resultType 期望从这条语句种返回结果的类全限定名或别名。注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。resultType 和 resultMap之间只能同时使用一个
resultMap 对外部resultMap 的命名引用。结果映射是MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。resultType 和resultMap 只能同时使用一个。
userCache 将其设置为true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对select 元素为true。
flushCache 将其设置为true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false
timeout 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unse) (依赖数据库驱动)。
fetchsize 这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。默认值为未设置(unset)(依赖驱动)
statementType 可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
resultSetType FORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。
databaseId 如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。
resultOrdered 这个设置仅针对嵌套结果 select 语句:如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false
resultSets 这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。

11. 一对多处理

假设场景:

一个老师有多个学生

  1. 创建实体类

    创建学生实体类

/**
 * 学生实体类
 *
 * @author xp
 * @since 2020-04-11 13:30
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    /**
     * 学生id
     */
    private Integer id;

    /**
     * 学生姓名
     */
    private String name;

    /**
     * 教师id
     */
    private Integer tid;
}

​ 创建教师实体类

/**
 * 教师实体类
 *
 * @author xp
 * @since 2020-04-11 13:30
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {

    /**
     * 教师id
     */
    private Integer id;

    /**
     * 教师姓名
     */
    private String name;

    /**
     * 一个老师对应多个学生对象
     */
    private ArrayList<Student> students;
}
  1. 创建mapper接口
/**
 * 教师mapper接口
 *
 * @author xp
 * @since 2020-04-11 13:30
 */
public interface TeacherMapper {
    /**
     * 根据教师id查询教师信息
     *
     * @param id 教师id
     * @return 教师对象
     */
    Teacher getTeacherById(@Param("id") Integer id);
}
  1. 配置映射器
<!-- 按照结果嵌套处理 -->
<select id="getTeacherById" resultMap="getStudent">
    select t.id tid,t.name tname,s.id sid,s.name sname
    from teacher t
    inner join student s on t.id = s.tid
    where t.id = #{id};
</select>
<resultMap id="getStudent" type="Teacher">
    <result property="id" column="tid"/>
    <result property="name" column="tname"/>
    <!-- 复杂的属性:我们需要单独处理 对象:association 集合:collection
        javaType="":指定属性的类型
        集合中的泛型信息:我们使用ofType获取
    -->
    <collection property="students" ofType="Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <result property="tid" column="tid"/>
    </collection>
</resultMap>

<!-- 按照查询嵌套处理 -->
<select id="getTeacherById" resultMap="getStudent">
    select * from teacher where id = #{id};
</select>

<resultMap id="getStudent" type="Teacher">
    <collection property="students" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId" column="id"/>
</resultMap>

<select id="getStudentByTeacherId" resultType="Student">
    select * from student where tid = #{id};
</select>
  1. 测试
@Test
public void getTeacherById(){
    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()){
        TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);

        Teacher teacher = mapper.getTeacherById(1);
        System.out.println(teacher);
    }
}

小结

  1. 关联 - association 【多对一】
  2. 集合 - collection 【一对多】
  3. javaType 和 ofType
    1. javaType 用来指定实体类中属性的类型
    2. ofType 用来指定映射到List或者集合中的 pojo 类型,泛型中的约束类型

注意点:

  • 保证SQL的可读性,尽量保证通俗易懂
  • 注意一对多和多对一中属性名和字段的问题
  • 如果问题不好排查错误,可以使用日志,建议使用Log4j

结果映射(resultMap)

  • constructor:用于在实例化类时,注入结果到构造方法中
    • idArg:ID参数;标记出作为ID的结果可以帮助提高正体性能
    • arg:将被注入到构造方法的一个普通结果
  • id:一个ID结果;标记出作为ID的结果可以帮助提高整体性能
  • result:注入到字段或JavaBean属性的普遍结果
  • association:一个复杂类型的关联;许多结果将包装成这种类型
    • 嵌套结果映射:关联可以时resultMap元素,或是对其它结果映射的引用
  • collection:一个复杂类型的集合
    • 嵌套结果映射:集合可以时resultMap元素,或是对其它结果映射的引用
  • discriminator:使用结果值来决定使用哪个resultMap
    • case:基于某些值的结果映射
      • 嵌套结果映射:case也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射。

12. 动态 SQL

12.1 什么是动态SQL?

动态 SQL 就是指根据不同的条件生成不同的SQL语句

动态 SQL 是 MyBatis的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接SQL语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态SQL,可以彻底摆脱这种痛苦。

MyBatis 3 的元素种类

  • if
  • choose(when,otherwise)
  • trim(where,set)
  • foreach

12.2 搭建环境

  1. 创建表并准备数据
# 创建表
create table if not exists blog(
    id varchar(50) not null comment '博客id',
    title varchar(100) not null comment '博客标题',
    author varchar(30) not null comment '博客作者',
    create_time datetime not null comment '创建时间',
    views int(30) not null comment '浏览量'
);
# 准备数据
insert into blog (id,title,author,create_time,views)
values (1,'java','xp',now(),999),
       (2,'mysql','xp',now(),999),
       (3,'oracle','xp',now(),999),
       (4,'spring','xp',now(),999),
       (5,'text','xp',now(),299);
  1. 创建实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Blog {
    /**
     * 博客id
     */
    private Integer id;
    /**
     * 博客标题
     */
    private String title;
    /**
     * 博客作者
     */
    private String author;
    /**
     * 创建时间
     */
    private Timestamp createTime;
    /**
     * 浏览量
     */
    private Integer views;
}
  1. 创建接口
/**
 * 博客Mapper
 *
 * @author xp
 * @since 2020-04-12 15:12
 */
public interface BlogMapper {
    /**
     * 通过if标签查询符合条件的博客信息
     *
     * @param map 存储查询条件
     * @return 符合条件的博客信息
     */
    ArrayList<Blog> selectBlogIf(Map<String, Object> map);

    /**
     * 通过where标签和if标签查询符合条件的博客信息
     *
     * @param map 存储查询条件
     * @return 符合条件的博客信息
     */
    ArrayList<Blog> selectBlogWhere(Map<String, Object> map);

    /**
     * 通过set标签更新博客数据
     *
     * @param map 存储set的字段名和字段值
     * @return 影响行数
     */
    int updateBlogSet(Map<String, Object> map);
    
     /**
     * 根据forEach标签查询符合条件的博客
     *
     * @param map 存储forEach标签中的集合元素
     * @return 符合条件的博客信息
     */
    ArrayList<Blog> selectBlogForEach(Map<String, Object> map);
}

12.3 if标签

  1. 在mapper中编写SQL映射语句
<select id="selectBlogIf" parameterType="map" resultType="blog">
select * from blog where 1=1
<if test="title != null">
and title = #{title}
</if>
<if test="author != null">
and author = #{author}
</if>
;
</select>
  1. 测试
@Test
public void selectBlogIf(){
    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()){
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

        Map<String, Object> map = new HashMap<>();
        map.put("title","java");
        map.put("author","xp");
        List<Blog> blogs = mapper.selectBlogIf(map);
        blogs.forEach(System.out::println);
    }
}

12.4 where标签

针对上面 if 标签中的例子,where 1=1是无用的垃圾代码,并没有实际意义,所以引入where标签对齐优化

  1. 在mapper中编写SQL映射语句
<select id="selectBlogWhere" parameterType="map" resultType="blog">
    select * from blog
    <where>
        <if test="title != null">
            title = #{title}
        </if>
        <if test="author != null">
            author = #{author}
        </if>
        ;
    </where>
</select>
  1. 测试
@Test
public void selectBlogWhere(){
    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()){
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

        Map<String, Object> map = new HashMap<>();
        map.put("title","java");
        ArrayList<Blog> blogs = mapper.selectBlogWhere(map);
        blogs.forEach(System.out::println);
    }
}

where 元素只会在子元素返回任何内容的情况下才插入“WHERE”子句。而且,若子句的开头有“AND”或“OR”,where 元素也会将它们去除。如果 where 元素不能达到自己的预期效果,可以自定义 trim 标签。

12.5 set标签

  1. 在mapper中编写SQL映射语句
<update id="updateBlogSet" parameterType="map">
    update blog
    <set>
        <if test="title != null">
            title = #{title}
        </if>
        <if test="author != null">
            ,author = #{author}
        </if>
    </set>
    where id = #{id};
</update>
  1. 测试
@Test
public void updateBlogSet(){
    try (SqlSession sqlSession= MyBatisUtil.getSqlSession()){
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

        Map<String,Object> map = new HashMap<>();
        map.put("id",3);
        map.put("title","oracle1");
        map.put("author","xp1");
        int row = mapper.updateBlogSet(map);
        sqlSession.commit();
        if (row!=0){
            System.out.println("修改成功");
        }else {
            System.out.println("修改失败");
        }
    }
}

set 元素会动态地在行首插入 SET 关键字,并且会删掉额外的逗号(这些逗号实在使用条件语句给列赋值时引入的)。

12.6 trim标签

where 标签和 set 标签时trim标签的一种具体实现。若where 和 set 标签不能达到自己的预期,则可以自定义 trim 标签实现。

trim自定义如下

<trim prefix="xxx" prefixOverrides="xxx" suffix="xxx" suffixOverrides="xxx">
  ...
</trim>
  • prefix:给 sql 语句拼接前缀
  • prefixoverrides:覆盖sql语句前面的关键字,关键字由prefixoverrides的值指定
  • suffix:给 sql 语句拼接后缀
  • suffixOverrides:覆盖sql语句后面的关键字,关键字由suffixOverrides的值指定

12.7 choose(when,otherwise)标签

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

12.8 forEach标签

动态 SQL 的另一个常见使用场景时对集合进行遍历(尤其是在构建IN条件语句的时候)。

  1. 在mapper中编写 SQL语句 映射
    <select id="selectBlogForEach" parameterType="map" resultType="blog">
        select * from blog
        <where>
            <foreach collection="ids" item="id" open="(" separator="or" close=")">
                id = #{id}
            </foreach>
        </where>
        ;
    </select>
  1. 测试
@Test
public void selectBlogForEach(){
    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()){
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);

        List<Integer> ids = new ArrayList<>();
        ids.add(1);
        ids.add(2);
        Map<String,Object> map = new HashMap<>();
        map.put("ids",ids);

        ArrayList<Blog> blogs = mapper.selectBlogForEach(map);
        blogs.forEach(System.out::println);
    }
}

forEach 元素的共嗯那个非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符。

提示:可以将任何可迭代对象(入List、Set等)、Map 对象或者数组对象作为集合参数传递给 forEach 。当使用可迭代对象或者数组时,index是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用Map对象(或者Map.Entry对象的集合)时,index时键,item是值。

12.9 SQL片段

有时候,我们可能会将一些功能的部分抽取出来,方便复用

对 if 标签、 where 标签和 set 标签中的例子进行优化

  1. 使用 sql 标签抽取公共的部分
<sql id="if_title_author">
    <if test="title != null">
        and title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</sql>
  1. 使用 include 标签引入 sql 标签的功能
<!-- sql标签将复用的代码提取出来 -->
<sql id="if_title_author">
    <if test="title != null">
        and title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</sql>

<select id="selectBlogIf" parameterType="map" resultType="blog">
    select * from blog where 1=1
    <include refid="if_title_author"/>
    ;
</select>

<select id="selectBlogWhere" parameterType="map" resultType="blog">
    select * from blog
    <where>
        <include refid="if_title_author"/>
        ;
    </where>
</select>

<update id="updateBlogSet" parameterType="map">
    update blog
    <set>
        <include refid="if_title_author"/>
    </set>
    where id = #{id};
</update>

注意事项:

  • 最好基于单表来定义SQL片段
  • 不要存在where标签

13. 缓存

13.1 简介

  1. 什么是缓存
    • 存在内存中的临时变量
    • 将用户常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。
  2. 为什么使用缓存?
    • 减少和数据库数据库交互次数,减少系统开销,提高系统效率。
  3. 什么样的数据能使用缓存?
    • 经常查询并且不经常改变的数据

13.2 MyBatis缓存

  • MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。
  • MyBatis系统中默认定义了两级缓存:一级缓存二级缓存
    • 默认情况下,只有一级缓存开启。( SqlSession 级别的缓存,也称为本地缓存)
    • 二级缓存需要手动开启和配置,他是基于 namespace 级别的缓存。
    • 为了提高拓展性,MyBatis 定义了缓存结构 Cache。我们可以通过实现 Cache 接口来自定义二级缓存

13.3 一级缓存

  • 一级缓存也叫本地缓存:
    • 与数据库同义词会话期间查询到的数据会放在本地缓存中。
    • 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库。

测试:

@Test
public void cacheTest(){
    // 第一个 SqlSession
    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()){
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        User user1 = mapper.selectUserById(3);
        User user2 = mapper.selectUserById(3);
        User user3 = mapper.selectUserById(4);
        System.out.println("user1:"+user1+"\thashcode:"+user1.hashCode());
        System.out.println("user2:"+user2+"\thashcode:"+user2.hashCode());
        System.out.println("user3:"+user3+"\thashcode:"+user3.hashCode());
    }
    // 第二个 SqlSession
    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()){
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        User user1 = mapper.selectUserById(3);
        System.out.println("user1:"+user1+"\thashcode:"+user1.hashCode());
    }
}

控制台日志输出结果如下:

image-20200412185633198

可以看到在第一个 SqlSession 中调用了3次selectUserById()方法查询数据库,但是在日志输出中可以看出,实际上只查询了两次数据库。说明了MyBatise默认开启了一级缓存。在第二次SqlSession和第一次SqlSession输出的日志对比可以看出,一级缓存是SqlSession级别的,一级缓存只存在于一次数据库会话中,即一级缓存的生命周期是该sqlSession的open到close。

缓存失效的情况:

  1. 查询不同的东西

    如上面例子

  2. 增删改操作,可能会改变原来的数据,所以必定会刷新缓存!

@Test
public void cacheTest2(){
    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()){
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        User user1 = mapper.selectUserById(3);
        // 非查询语句
        mapper.insertUser(new User(5,"test","test"));
        User user2 = mapper.selectUserById(3);

        System.out.println("user1:"+user1+"\thashcode:"+user1.hashCode());
        System.out.println("user2:"+user2+"\thashcode:"+user2.hashCode());

    }
}

​ 控制台输出结果:

image-20200412191207013

  1. 查询不同的 Mapper.xml
  2. 手动清理缓存!
// 清除该sqlSession的缓存
sqlSession.clearCache();

13.4 二级缓存

  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
  • 基于 namespace 级别的缓存,一个名称空间,对应一个二级缓存
  • 工作机制
    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
    • 如果当前会话关闭了,这个会话对应的一级缓存就没了。但是我们想要的是,会话关闭了,一级缓存中的数据被保存在二级缓存中
    • 新的会话查询信息,就可以从二级缓存中获取内容
    • 不同的 mapper 查出的数据会放在自己对应的缓存(map)中

步骤:

  1. 在核心配置文件 mybatis-config.xml 文件中开启全局缓存
<!-- 设置 -->
<settings>
    <!-- 设置日志实现 -->
    <setting name="logImpl" value="LOG4J"/>
    <!-- 开启缓存 -->
    <setting name="cacheEnabled" value="true"/>
</settings>
  1. 在需要开启二级缓存的 mapper 中开启二级缓存
<!-- 开启默认二级缓存 -->
<cache/>

​ 官网中的例子

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>
  1. 测试

    测试代码

@Test
public void cacheTest3(){
    // 第一个 SqlSession
    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()){
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        User user1 = mapper.selectUserById(3);
        User user2 = mapper.selectUserById(3);
        
        System.out.println("user1:"+user1+"\thashcode:"+user1.hashCode());
        System.out.println("user2:"+user2+"\thashcode:"+user2.hashCode());
    }
    // 第二个 SqlSession
    try (SqlSession sqlSession = MyBatisUtil.getSqlSession()){
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        User user1 = mapper.selectUserById(3);
        System.out.println("user1:"+user1+"\thashcode:"+user1.hashCode());
    }
}

​ 控制台输出截图:

image-20200413000601497

​ 报错:

org.apache.ibatis.cache.CacheException: Error serializing object.  Cause: java.io.NotSerializableException: com.xp.model.entity.User
Caused by: java.io.NotSerializableException: com.xp.model.entity.User

若报这个错误,则是因为实体类没有序列化。将查询的数据存入缓存(本地内存)再从缓存中取出,就需要序列化和反序列化的过程。所以必须让实体类实现 Serializable 接口。

上图可以看到,再第二次会话时,对象的哈希值和原对象不同。这是因为序列化是深拷贝,所以反序列化后的对像和原对象不是同一个对象,故哈希值不同。

若要两次会话查询的是同一个对象,则需要将 cache 中 readyonly 属性设置为 true

<cache readyonly="true"/>

总结:

  • 只要开启了一级缓存,在同一个Mapper下就有效
  • 所有的数据都会先放在一级缓存中
  • 只有当会话提交或者关闭的时候,才会提交到二级缓存中

13.5 缓存原理

image-20200413002631760

13.6 自定义缓存

见官网

用 redis 缓存代替

14. MyBatis 逆向工程

逆向工程,又称反向工程,其实就是 MyBatis 官方提供的一种工具。

当我们项目很大的时候,表很多的时候,我们就需要创建很多个文件,比如实体类,mapper接口,xml等。

所谓 MyBatis 逆向工程,就是可以一键生成数据库表对应的所有的实体类、Mapper文件、Mapper接口,里面的增删改查的方法也全部搞定!

1. MyBatis 逆向工程简介

MyBatis Generator(MBG)是 MyBatis 和 iBATIS 的代码生成器。它将为所有版本的 MyBatis 以及版本 2.2.0 之后的 iBATIS 版本生成代码。它将内省数据库表(或许多表),并将生成可用于访问表的工件。这减少了设置对象和配置文件以及数据库表交互的初始麻烦。MBG 寻求对简单 CRUD (创建,检索,更新,删除)的大部分数据库操作产生重大影响。您仍然需要为链接查询或存储过程手动编写 SQL 和对象代码

官网:http://mybatis.org/generator/

2. 逆向工程使用

  1. 创建 Maven 工程

  2. 引入依赖

    <!-- mysql -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.46</version>
    </dependency>
    <!-- mybatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.5</version>
    </dependency>
    <!-- junit 测试包 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <!-- MyBatis 逆向工程生成器 -->
    <dependency>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-core</artifactId>
        <version>1.4.0</version>
    </dependency>
    
  3. 编写逆向工程生成的 xml 配置文件

    在 resources 目录下创建 generator.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>
        <context id="testTables" targetRuntime="MyBatis3">
            <commentGenerator>
                <!-- 是否去除自动生成的注释 true:是 : false:否 -->
                <property name="suppressAllComments" value="true" />
            </commentGenerator>
            <!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
            <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                            connectionURL="jdbc:mysql://localhost:3306/mybatis-demo?useSSL=false"
                            userId="root"
                            password="root">
            </jdbcConnection>
            <!-- <jdbcConnection driverClass="oracle.jdbc.OracleDriver"
                connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:yycg"
                userId="yycg"
                password="yycg">
            </jdbcConnection> -->
    
            <!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 和
                NUMERIC 类型解析为java.math.BigDecimal -->
            <javaTypeResolver>
                <property name="forceBigDecimals" value="false" />
            </javaTypeResolver>
    
            <!-- targetProject:生成实体类的位置 -->
            <javaModelGenerator targetPackage="com.xp.model.entity"
                                targetProject=".\src\main\java">
                <!-- enableSubPackages:是否让schema作为包的后缀 -->
                <property name="enableSubPackages" value="false" />
                <!-- 从数据库返回的值被清理前后的空格 -->
                <property name="trimStrings" value="true" />
            </javaModelGenerator>
            <!-- targetProject:mapper映射文件生成的位置 -->
            <sqlMapGenerator targetPackage="mapper"
                             targetProject="src\main\resources">
                <!-- enableSubPackages:是否让schema作为包的后缀 -->
                <property name="enableSubPackages" value="false" />
            </sqlMapGenerator>
            <!-- targetPackage:mapper接口生成的位置 -->
            <javaClientGenerator type="XMLMAPPER"
                                 targetPackage="com.xp.mapper"
                                 targetProject=".\src\main\java">
                <!-- enableSubPackages:是否让schema作为包的后缀 -->
                <property name="enableSubPackages" value="false" />
            </javaClientGenerator>
            <!-- 指定数据库表 -->
            <table tableName="user"/>
    
            <!-- 有些表的字段需要指定java类型
             <table schema="" tableName="">
                <columnOverride column="" javaType="" />
            </table> -->
        </context>
    </generatorConfiguration>
    
  4. 编写逆向工程测试类

    public class GeneratorTest {
    	// 逆向工程生成
        @Test
        public void generator() throws Exception{
            List<String> warnings = new ArrayList<String>();
            boolean overwrite = true;
            // 如果相对路径出错,则改成绝对路径
            File configFile = new File("src/main/resources/generator.xml");
            ConfigurationParser cp = new ConfigurationParser(warnings);
            Configuration config = cp.parseConfiguration(configFile);
            DefaultShellCallback callback = new DefaultShellCallback(overwrite);
            MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,
                    callback, warnings);
            myBatisGenerator.generate(null);
        }
    }
    
  5. 编写 MyBatis 核心配置文件

    逆向工程可以生成数据库表对应的实体类、mapepr接口、mapper.xml 文件,但是并不能创建 MyBatis 核心配置文件,所以我们需要手动创建 MyBatis 核心配置文件 mybatis-config.xml

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    
        <properties resource="db.properties"/>
    
        <!-- 别名 -->
        <typeAliases>
            <package name="com.xp.model"/>
        </typeAliases>
    
        <!-- 环境配置 -->
        <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>
    
        <!-- 注册 mapper -->
        <mappers>
            <mapper resource="mapper/UserMapper.xml"/>
        </mappers>
        
    </configuration>
    
  6. 测试

    将我们之前的 MyBatis 工具类拷贝过来,然后编写测试类,测试是否能成功执行 sql 语句

    @Test
    public void test(){
        // 获取 sqlSession,和 mapper接口的实现
        SqlSession session = MyBatisUtil.getSession();
        UserMapper mapper = session.getMapper(UserMapper.class);
        // 执行查询语句
        User user = mapper.selectByPrimaryKey(2);
        System.out.println(user);
    }
    
posted @ 2020-04-13 17:09  Windows_XP  阅读(201)  评论(0编辑  收藏  举报