mybatis入门基础

mybatis入门基础

1. mybatis简介

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

数据持久化

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

为什么需要持久化

  • 有一些对象,不能让他丢掉
  • 内存太贵
1.4 为什么需要mybatis
  • 帮助程序员将数据存到数据库中
  • 方便
  • 传统的JDBC代码太复杂了。简化 框架 自动化
  • 不用Mybatis也可以,mybayis更容易上手。

2. 创建一个mybatis项目

2.1 准备环境

1.准备数据库环境

  • 连接数据库

    mysql -u root -proot
    
  • 创建一个名字为mybatis的数据库

    create database mybatis;
    
  • 创建表user

    CREATE TABLE USER(
    `id` INT(11) NOT NULL PRIMARY KEY COMMENT '主键',
    `name` VARCHAR(50) NOT NULL COMMENT '姓名',
    `age` INT(3) NOT NULL COMMENT '年龄',
    `pwd` VARCHAR(50) NOT NULL COMMENT '密码'
    )COMMENT='用户表' ENGINE INNODB DEFAULT CHARSET=utf8;
    
  • 准备数据

INSERT INTO USER(`id`,`name`,`age`,`pwd`) 
VALUES(1,'张三',23,'123456'),
(2,'李四',24,'123456'),
(3,'张三',25,'123456');
2.2 创建一个maven项目

image-20210408105439002

image-20210408105506349

image-20210408105620282

maven修改成自己配置的版本

image-20210408110321308

刪掉src

image-20210408110433818

pom.xml中引入mysql启动包、mybatis包和junit测试包

image-20210408111531344

创建一个子模块

image-20210408112459400

创建完子模块后注意子模块的jdk版本,修改成jdk8,同事注意java编译的版本,都要修改成jdk8

image-20210409152025291

image-20210409152150548

再根据需求创建需要的文件夹,到此一个空项目就创建完成。

image-20210409152428746

2.3 配置mybatis

根据mybatis3中文文档,创建一个mybatis-config.xml配置文件,创建一个工具类用于加载配置文件创建SqlSessionFactory 。

image-20210409154058762

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>
    <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?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/xbm/dao/UserMapper.xml"/>
    </mappers>
</configuration>

工具类MybatisUtil代码如下:

public class MybatisUtil {
    static String resource = "mybatis-config.xml";

    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

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

生成实体类User以及持久层接口UserDao

public class User {
    private Integer id;
    /**
     * 姓名
     */
    private String name;

    /**
     * 年龄
     */
    private Integer age;

    /**
     * 密码
     */
    private String pwd;
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAge() {
        return age;
    }
    public void setAge(Integer age) {
        this.age = age;
    }
    public String getPwd() {
        return pwd;
    }
    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
}
public interface UserDao {
    List<User> getUseList(@Param("name") String name);
}

根据mybatis文档,我们需要创建一个mapper.xml映射文件

image-20210409154700024

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xbm.dao.UserDao">
    <select id="getUseList" resultType="com.xbm.entity.User" parameterType="String">
        select * from user where name = #{name}
    </select>
</mapper>

namespace:命名空间,指向dao的相对路径; 用来映射dao的;

resultType:返回结果的类型;

parameterType:传入参数类型;

resultMap:返回的结果集合;

parameterMap:传入的参数集合。

select、update、insert、delete等方法中的ID应该与dao中的方法名保持一致,否则会报错找不到该方法。

2.4 创建一个测试类进行测试

image-20210409155604320

public class TestDemo {
    @Test
    public void test01(){
        SqlSession sqlSession = SqlSessionUtil.getSqlSession();
        try{
            UserDao userDao = sqlSession.getMapper(UserDao.class);
            List<User> userList = userDao.getUseList("张三");
            userList.stream().forEach(e ->{
                System.out.println("id:"+e.getId()+"------age:"+e.getAge()+"-------name:"+e.getName());
            });
        }catch(Exception e){
           e.printStackTrace(); 
        }finally{
            sqlSession.close();
        }
    }
}

**注意 **:

maven 由于他的约定大于配置,配置文件约定是放在resources中,导致java中的配置文件无法导出或生效,pom.xml中加上以下配置可解决:

<build>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/**</include>
            </includes>
            <filtering>false</filtering>
        </resource>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

3. CRUD 和模糊查询

3.1 CRUD

UserDao

void insertUser(User user);

void deleteUser(int userId);

void updateUser(User user);

List<User> getUserListByAge(Map<String,Object> map);

List<User> getUserListByName(@Param("userNameMap") Map<String,Object> userNameMap);

UserMapper.xml

<!-- 对象中的属性可以直接取出来-->
<insert id="insertUser" parameterType="com.xbm.entity.User">
    insert into user(id,`name`,`age`,`pwd`) values(#{id},#{name},#{age},#{pwd})
</insert>

<delete id="deleteUser" parameterType="int">
    delete from user where id=#{userId}
</delete>

<update id="updateUser" parameterType="com.xbm.entity.User">
    update user set `name`=#{name},age=#{age},pwd=#{pwd} where id=#{id}
</update>

<select id="getUserListByAge" parameterType="map" resultType="com.xbm.entity.User">
    select * from user where age=#{age}
</select>

<select id="getUserListByName" parameterType="map" resultType="com.xbm.entity.User">
    select * from user where name=#{userNameMap.userName}
</select>

测试TestDemo.java

@Test
public void addUser(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    try{
        UserDao userDao = sqlSession.getMapper(UserDao.class);

        User user = new User();
        user.setId(4);
        user.setAge(18);
        user.setName("大神");
        user.setPwd("131231");
        userDao.insertUser(user);
        sqlSession.commit();
    }catch(Exception e){
        e.printStackTrace();
    }finally{
        sqlSession.close();
    }
}

@Test
public void deleteUser(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    UserDao userDao = sqlSession.getMapper(UserDao.class);
    userDao.deleteUser(1);
    sqlSession.commit();
}

@Test
public void updateUser(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    UserDao userDao = sqlSession.getMapper(UserDao.class);
    User user = new User(2,"战神",88,"123123");
    userDao.updateUser(user);

}

@Test
public void getUserListByAge(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    UserDao userDao = sqlSession.getMapper(UserDao.class);
    Map<String,Object> map = new HashMap<>();
    map.put("age",18);
    List<User> userList = userDao.getUserListByAge(map);
    userList.stream().forEach( e ->{
        System.out.println(e.getName());
    });
}

@Test
public void getUserListByName(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    UserDao userDao = sqlSession.getMapper(UserDao.class);
    Map<String,Object> map = new HashMap<>();
    map.put("userName","大神");
    List<User> userList = userDao.getUserListByName(map);
    userList.stream().forEach( e ->{
        System.out.println(e.getName());
    });
}

**注意 **:(1)增删改都需要提交事物

​ (2)如果Dao中的参数Map没有用@Param修饰,mapper.xml中取参数的时候直接用#{key}

​ (3)如果Dao中的参数Map用@Param修饰了,mapper.xml中取参数的时候需要接用#{map名.key}

3.2 模糊查询

UserDao

List<User> getUserLike(@Param("userName") String userName);

List<User> getUserLike2(@Param("userName") String userName);

List<User> getUserLike3(@Param("userName") String userName);

UserMapper.xml

<select id="getUserLike" parameterType="String" resultType="com.xbm.entity.User">
    select * from user where name like #{userName}
</select>

<select id="getUserLike2" parameterType="String" resultType="com.xbm.entity.User">
    select * from user where name like "%"#{userName}"%"
</select>

<select id="getUserLike3" parameterType="String" resultType="com.xbm.entity.User">
    select * from user where name like CONCAT("%",#{userName},"%")
</select>

测试

@Test
public void getUserLike(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    UserDao userDao = sqlSession.getMapper(UserDao.class);
    List<User> userList =  userDao.getUserLike("%张%");
    userList.stream().forEach( e ->{
        System.out.println(e.getName());
    });
}

@Test
public void getUserLike2(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    UserDao userDao = sqlSession.getMapper(UserDao.class);
    List<User> userList =  userDao.getUserLike2("三");
    userList.stream().forEach( e ->{
        System.out.println(e.getName());
    });
}

@Test
public void getUserLike3(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    UserDao userDao = sqlSession.getMapper(UserDao.class);
    List<User> userList =  userDao.getUserLike3("大");
    userList.stream().forEach( e ->{
        System.out.println(e.getName());
    });
}

**总结 **:模糊查询有两种方式,一个是在java代码中将通配符”%“与参数拼接上,另外一种是在Mapper.xml中将通配符与参数拼接上。

4. 配置解析

4.1 核心配置 mybatis-config.xml
(1)环境配置 environments

​ MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景。

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

​ 所以,如果你想连接两个数据库,就需要创建两个 SqlSessionFactory 实例,每个数据库对应一个。而如果是三个数据库,就需要三个实例,依此类推,记起来很简单:

每个数据库对应一个 SqlSessionFactory 实例

mybatis-config.xml

<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?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
            <property name="username" value="root"/>
            <property name="password" value="root"/>
        </dataSource>

    </environment>

    <environment id="test">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=utf8"/>
            <property name="username" value="root"/>
            <property name="password" value="root"/>
        </dataSource>

    </environment>
</environments>

MybatisUtil.java

public class MybatisUtil {
    static String resource = "mybatis-config.xml";

    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
}
  • 事物管理器 transactionManager

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

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

    MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周

(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。例如:

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

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

  • 数据源 datasource

dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):

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

  • defaultTransactionIsolationLevel – 默认的连接事务隔离级别。
  • defaultNetworkTimeout – 等待数据库操作完成的默认网络超时时间(单位:毫秒)。

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

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

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

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

  • initial_context – 这个属性用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))。这是个可选属性,如果忽略,那么将会直接从 InitialContext 中寻找 data_source 属性。
  • data_source – 这是引用数据源实例位置的上下文路径。提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找。

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

  • env.encoding=UTF8

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

可以通过实现接口 org.apache.ibatis.datasource.DataSourceFactory 来使用第三方数据源实现:

public interface DataSourceFactory {
  void setProperties(Properties props);
  DataSource getDataSource();
}

org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory 可被用作父类来构建新的数据源适配器,比如下面这段插入 C3P0 数据源所必需的代码:

import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
import com.mchange.v2.c3p0.ComboPooledDataSource;

public class C3P0DataSourceFactory extends UnpooledDataSourceFactory {

  public C3P0DataSourceFactory() {
    this.dataSource = new ComboPooledDataSource();
  }
}

为了令其工作,记得在配置文件中为每个希望 MyBatis 调用的 setter 方法增加对应的属性。 下面是一个可以连接至 PostgreSQL 数据库的例子:

<dataSource type="org.myproject.C3P0DataSourceFactory">
  <property name="driver" value="org.postgresql.Driver"/>
  <property name="url" value="jdbc:postgresql:mydb"/>
  <property name="username" value="postgres"/>
  <property name="password" value="root"/>
</dataSource>
(2)properties配置文件

db.properties

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8
username=root
password=root

mybaties-config.xml

<properties resource="db.properties"/>
<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>
    <environment id="test">
        <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>

properties 中也可以即引入properties文件又定义property属性

<properties resource="db.properties">
    <property name="username" value="root"/>
    <property name="password" value="123456"/>
</properties>

**如果property中的属性名与引入的properties文件中的属性名形同,以properties文件中的属性值为准 **

**注意 **:mybatis-config.xml中的属性顺序有规范,不可随意调整各属性之间的顺序否则会报错。

image-20210413145003767

(3)别名 typeAliases
  • 直接定义

mybatis-config.xml

<typeAliases>
    <typeAlias type="com.xbm.entity.User" alias="user" />
</typeAliases>

mapper.xml

image-20210413145944581

  • 扫描包

mybatis-config.xml

<typeAliases>
    <package name="com.xbm.entity"/>
</typeAliases>

第一种定义别名的方式可以DIY别名,第二种默认为第一个字母小写的实体类名称,第二种取别名的方式也可以通过@Alias标签DIY别名。

image-20210413150321881

(4)设置 setting

image-20210413151449292

image-20210413151528900

主要记住以上三个,其他的用到时再到文档中查找。

(5)映射器 mapper
  • 方式一:
<mappers>
    <mapper resource="com/xbm/dao/UserMapper.xml"/>
</mappers>
  • 方式二:使用class类绑定注册
<mappers>
    <mapper class="com.xbm.dao.UserMapper" />
</mappers>

注意点:

​ dao接口名与mapper.xml文件名必须相同

​ dao接口与mapper.xml文件必须在同一个文件夹下

  • 方式三:通过包扫描绑定注册
<mappers>
    <package name="com.xbm.dao"/>
</mappers>

注意点:

​ dao接口名与mapper.xml文件名必须相同

​ dao接口与mapper.xml文件必须在同一个文件夹下

4.2 生命周期与作用域

image-20210413160331492

**SqlSessionFactoryBuilder: **

  • 一旦创建了SqlSessionFactory就不需要它了
  • 局部变量

**SqlSessionFactory: **

  • 可以理解为一个数据库连接池
  • 一旦创建就应在在应用运行期间一直存在,**没有理由丢弃它或另外创建一个 **
  • 因此它的作用域是应用作用域
  • 最简单的就是单例模式或静态单例模式

**SqlSession: **

  • 连接到连接池的一个请求
  • SqlSession实例不是线程安全的,因此不能被共享,因此它的最佳作用域是请求或方法作用域
  • 用完之后需要关闭,否则资源被浪费
4.3 解决属性名与字段名不一致的问题

再建一个项目名字为mybatis02,将mybatis01中的类都复制过来,只改变User.java中的属性,如下图:

image-20210413165431406

执行测试方法,发现返回的User对象中password属性为null,如下图:

image-20210413165608547

如何解决:

**1. 使用别名: **

​ 原SQL

<select id="getUseList" resultType="user" parameterType="String">
    select * from user where name = #{name}
</select>

使用别名后的SQL

<select id="getUseList" resultType="user" parameterType="String">
    select id,`name`,age,pwd password from user where name = #{name}
</select>

解决

image-20210413165939563

  • 2.使用resultMapper

mapper.xml中创建一个resultMap,如下图

image-20210413170405047

  • resultMap 元素是 MyBatis 中最重要最强大的元素

  • ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。

  • ResultMap 的优秀之处——你完全可以不用显式地配置它们,只配了不同的字段

    image-20210413171251125

  • 如果这个世界总是这么简单就好了。

5. 日志工厂

如果一个数据库操作出现了报错,我们需要排错,就需要用到日志。Mybatis 通过使用内置的日志工厂提供日志功能。内置日志工厂将会把日志工作委托给下面的实现之一:

  • SLF4J
  • LOG4J 【掌握】
  • LOG4J2
  • JDK_LOGGING
  • COMMONS_LOGGING
  • STDOUT_LOGGING 【掌握】
  • NO_LOGGING
5.1 STDOUT_LOGGING标准日志输出

mybatis-config.xml配置日志

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

image-20210414105135521

5.2 Log4j

什么是Log4j

  • Log4j是Apache的一个开源项目,通过使用Log4j,可以控制日志信息输出到控制台、文件、GUI组件
  • 可以控制每一条日志的输出格式
  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程
  • 可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码

Log4j使用步骤

​ 01.导入Log4j包

<!-- 加入log4j支持 -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

​ 02.resources下创建log4j.properties配置文件

#将等级为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/kuang.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

03.mybatis-config.xml配置

<settings>
    <setting name="logImpl" value="LOG4J"/>
</settings>

image-20210414111215117

简单使用Logger

image-20210414111643610

6. 分页

为什么要分页:减少数据的处理量

6.1 使用limit分页
  • 语法
select * from user limit startIndex,pageSize//startIndex查询的起始位置pageSize每页数量
select * from user limit 3 == select * from user limit 0,3

mapper接口

List<User> getUserByPage(Map<String,Integer> map);

mapper.xml

<select id="getUserByPage" parameterType="map" resultMap="userMap">
    select id,`name`,age,pwd from user limit #{startIndex},#{pageSize}
</select>

测试方法

@Test
public void test02(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    try{
        UserMapper userDao = sqlSession.getMapper(UserMapper.class);
        Map<String,Integer> map = new HashMap<>();
        map.put("startIndex",0);
        map.put("pageSize",2);
        List<User> userList = userDao.getUserByPage(map);
        userList.stream().forEach(e ->{
            System.out.println(e.toString());
        });
    }catch(Exception e){
        e.printStackTrace();
    }finally{
        sqlSession.close();
    }
}
6.2 使用RowBounds分页

不用写分页SQL

mapper接口

List<User> getUserListByPage();

mapper.xml

<select id="getUserListByPage" resultMap="userMap">
    select id,`name`,age,pwd from user
</select>

测试类

@Test
public void test03(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    try{
        RowBounds rowBounds = new RowBounds(1,2);//从第一个开始查,每页2个
        List<User> userList = sqlSession.selectList("com.xbm.dao.UserMapper.getUserListByPage",null,rowBounds);
        userList.stream().forEach(e ->{
            System.out.println(e.toString());
        });
    }catch(Exception e){
        e.printStackTrace();
    }finally{
        sqlSession.close();
    }
}
6.3 分页插件

image-20210414142312937

来源:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/README_zh.md

7. 使用注解开发

7.1 使用注解开发

mapper接口

@Select("select id,name,age,pwd from user where id=#{id}")
List<User> getUserListById(@Param("id") int id);

测试类

@Test
public void test04(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    try{
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
        List<User> userList = userMapper.getUserListById(2);
        userList.stream().forEach(e ->{
            System.out.println(e.toString());
        });
    }catch(Exception e){
        e.printStackTrace();
    }finally{
        sqlSession.close();
    }
}

关于@Param() 注解

  • 基本类型的参数或者String类型,需要加上
  • 引用类型不需要加
  • 如果只有一个基本类型的话,可以忽略,但是建议大家都加上!
  • 我们在SQL中引用的就是我们这里的 @Param() 中设定的属性名!

8. lombok

​ 一个可以帮助实体例省去get、set、构造方法等方法的插件。

使用步骤:

1.下载插件

image-20210415152135914

2.导入包

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.10</version>
</dependency>

3.实体类上加注解

@Data    //无参构造,get、set、tostring、hashcode,equals
@AllArgsConstructor//带所有属性的有参构造方法
@NoArgsConstructor//无参构造方法
public class User {
    private Integer id;

    private String name;

    private Integer age;

    private String password;
}

9. 多对一

数据库脚本

CREATE TABLE `teacher` (
  `id` INT(10) NOT NULL,
  `name` VARCHAR(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO teacher(`id`, `name`) VALUES (1, '秦老师'); 
CREATE TABLE `student` (
  `id` INT(10) NOT NULL,
  `name` VARCHAR(30) DEFAULT NULL,
  `tid` INT(10) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fktid` (`tid`),
  CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('1', '小明', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('2', '小红', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('3', '小张', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('4', '小李', '1'); 
INSERT INTO `student` (`id`, `name`, `tid`) VALUES ('5', '小王', '1');

实体类

@Data
public class Student {
    private int id;
    private String name;
    private Teacher teacher;
}

@Data
public class Teacher {
    private int id;
    private String name;
}

mapper接口

public interface StudentMapper {
    List<Student> getStudentTeacher();

    List<Student> getStudentTeacher2();
}

mapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xbm.dao.StudentMapper">
    <!--按查询嵌套处理
        1.查询所有学生
        2.根据学生中的tid查询对应的老师
    -->
    <resultMap id="studentTeacher" type="student">
        <result property="id" column="id" />
        <result property="name" column="name" />
        <!--复杂的属性,我们需要单独处理 对象: association 集合: collection -->
        <association property="teacher" column="tid" select="getTeacher"/>
    </resultMap>

    <select id="getStudentTeacher" resultMap="studentTeacher">
        select id,`name`,tid from student
    </select>

    <select id="getTeacher" parameterType="_int" resultType="teacher">
        select id,`name` from teacher where id=#{tid}
    </select>

    <!--按结果嵌套处理-->
    <resultMap id="studentTeacher2" type="com.xbm.entity.Student">
        <result  property="id" column="sid"/>
        <result property="name" column="sname" />
        <!--复杂的属性,我们需要单独处理 对象: association 集合: collection -->
        <association property="teacher">
            <result property="id" column="tid" />
            <result property="name" column="tname" />
        </association>
    </resultMap>
    <select id="getStudentTeacher2" resultMap="studentTeacher2">
        select s.id sid,s.name sname,t.id tid,t.name tname
        from student s,teacher t where s.tid=t.id
    </select>

</mapper>

测试方法

@Test
public void test(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
	//List<Student> studentList = studentMapper.getStudentTeacher();
    List<Student> studentList = studentMapper.getStudentTeacher2();

    studentList.stream().forEach( e ->{
        System.out.println(e);
    });
}

按查询嵌套处理

image-20210415211402588

查询结果输出:

image-20210415211605871

两个查询语句,相当于嵌套了一个子查询。

结果嵌套处理(推荐使用)

image-20210415211947432

查询结果输出:

image-20210415212140592

一个查询语句,相当于联表查询

10. 一对多

实体类

@Data
public class Student {
    private int id;
    private String name;
    private int tid;
}
@Data
public class Teacher {
    private int id;
    private String name;
    private List<Student> studentList;
}

mapper接口

public interface TeacherMapper {

    Teacher getTeacherById(@Param("id") int id);

    Teacher getTeacherById2(@Param("id") int id);
}

mapper.xml

<!--按查询嵌套处理
        1.查询id=1的老師
        2.根据老师中的id查询学生集合
    -->
<resultMap id="teacherStudent" type="teacher">
    <result property="id" column="id" />
    <result property="name" column="name" />
    <collection property="studentList" column="id" select="getStudent" />
</resultMap>

<select id="getTeacherById" resultMap="teacherStudent" parameterType="_int">
    select id,`name`,tid from student where  id=#{id}
</select>

<select id="getStudent" parameterType="_int" resultType="student">
    select id,`name`,tid from student where tid=#{id}
</select>


<!--按结果嵌套处理-->
<resultMap id="teacherStudent2" type="teacher">
    <result property="id" column="tid" />
    <result property="name" column="tname" />
    <!--复杂的属性,我们需要单独处理 对象: association 集合: collection
        javaType="" 指定属性的类型! 可以省略
        集合中的泛型信息,我们使用ofType获取 ofType一定不能省略否则会报错(空指针)
        -->
    <collection property="studentList" ofType="student">
        <result property="id" column="sid" />
        <result property="name" column="sname" />
        <result property="tid" column="stid" />
    </collection>

</resultMap>
<select id="getTeacherById2" parameterType="_int" resultMap="teacherStudent2">
    select t.id tid,t.name tname,s.id sid,s.name sname,s.tid stid
    from teacher t,student s where t.id=s.tid
</select>

测试方法

 @Test
public void test2(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
    //Teacher teacher = mapper.getTeacherById(1);
    Teacher teacher = mapper.getTeacherById2(1);
    System.out.println(teacher);
}

小结

关联 - association 【多对一】

集合 - collection 【一对多】

property:与实体类中属性名一致;

column:与数据库中字段名一致或查询语句中别名一致;

javaType:property的类别(可以省略)

jdbcType:column类别(可以省略)

ofType:用来指定映射到List或者集合中的 pojo类型,泛型中的约束类型!(不可省略)

12. 动态SQL

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

动态 SQL 元素和 JSTL 或基于类似 XML 的文本处理器相似。在 MyBatis 之前的版本中,有很多元素需要花时间了解。MyBatis 3 大大精简了元素种类,现在只需学习原来一半的元素便可。MyBatis 采用功能强大的基于 OGNL 的表达式来淘汰其它大部分元素。

SQL 脚本

CREATE TABLE `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 '浏览量'
) ENGINE=InnoDB DEFAULT CHARSET=utf8

实体类Blog.java

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Blog {

    private String id;

    private String title;

    private String author;

    private Date createTime;

    private int views;
}
12.1 IF

mapper接口

List<Blog> getBlogList(@Param("title") String title);

mapper.xml

<select id="getBlogList" parameterType="String" resultType="blog">
    select * from blog where 1=1
    <if test="title != null and title != ''">
        and title = #{title}
    </if>
</select>

测试类

@Test
public void test3(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    //List<Blog> blogList = mapper.getBlogList(null);
    List<Blog> blogList = mapper.getBlogList("mybatis入门2");
    blogList.stream().forEach(e ->{
        System.out.println(e);
    });
}
12.2 choose (when, otherwise)

mapper接口

List<Blog> getBlogListChoose(Map<String,Object> map);

mapper.xml

<select id="getBlogListChoose" parameterType="String" resultType="blog">
    select * from blog
    <where>
        <choose >
            <when test="title != null and title != ''">
                title = #{title}
            </when>
            <when test="author != null and author != ''">
                and author = #{author}
            </when>
            <otherwise>
                and views >= #{views}
            </otherwise>
        </choose>

    </where>
</select>

有where 标签会自动匹配 and关键字,第一个when 不需要写and,其他的条件都需要写and

测试方法

@Test
public void test04(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    Map<String,Object> map = new HashMap<>();
    //map.put("title","");
    map.put("title","mybatis入门3");
    //map.put("author","");
    map.put("author","xbm");
    map.put("views",4000);
    List<Blog> blogList = mapper.getBlogListChoose(map);
    blogList.stream().forEach(e ->{
        System.out.println(e);
    });
}
12.3 trim (where,set)

mapper.xml

<select id="getBlogList2" parameterType="map" resultType="blog">
    select * from blog
    <where>
        <if test="title != null and title != ''">
            title = #{title}
        </if>
        <if test="author != null and author != ''">
            and author = #{author}
        </if>
    </where>
</select>

<update id="updateBlog" parameterType="map">
    update blog
    <set>
        <if test="title != null and title != ''">
            title = #{title},
        </if>
        <if test="author != null and author != ''">
            author = #{author}
        </if>
    </set>
    where id=#{id}
</update>

所谓的动态SQL,本质还是SQL语句 , 只是我们可以在SQL层面,去执行一个逻辑代码

12.4 SQL片段

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

01.使用SQL标签抽取公共的部分

<sql id="if-title-author">
    <if test="title != null and title != ''">
        title = #{title},
    </if>
    <if test="author != null and author != ''">
        author = #{author}
    </if>
</sql>

02.在需要使用的地方使用Include标签引用即可

<update id="updateBlog" parameterType="map">
    update blog
    <set>
        <include refid="if-title-author" />
    </set>
    where id=#{id}
</update>

注意事项:

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

将SQL: select * from blog where id=1 or id=2 or id=3 改成动态SQL

<select id="getBlogListByIdList" resultType="blog">
    select * from blog where
    <foreach collection="idList" item="id" open="(" separator="or" close=")">
        id = #{id}
    </foreach>
</select>

image-20210416132524816

按照官方文档的解析:

img

collection :集合,值与mapper接口中的参数一致

item:集合中的对象,名字可自定义

open:以什么开头

separator:以什么分割

close:以什么结尾

13. 缓存 (了解即可)

13.1 简介

如果每次查询都需要连接数据库,将会耗大量资源!

一次查询 --->缓存------->再次查询不走数据库,从缓存中获取

什么是缓存 [ Cache ]?

  • 存在内存中的临时数据。
  • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。

为什么要用缓存

  • 减少和数据库的交互次数,减少系统开销,提高系统效率。

什么样的数据能使用缓存?

  • 经常查询并且不经常改变的数据。【可以使用缓存】
13.2 mybatis一级缓存

mybatis一级缓存也叫本地缓存,他是存在SqlSession中

  • 数据库同一次会话期间查询到的数据会放在本地缓存中
  • 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库
  • 默认是开启的

一级缓存比较鸡肋,它是在存在SqlSession会话中的,会话关闭缓存消失。

测试方法

@Test
public void test05(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    List<Integer> idList = new ArrayList<>();
    idList.add(1);
    idList.add(2);
    idList.add(3);
    List<Blog> blogList = mapper.getBlogListByIdList(idList);

    System.out.println("=======================");

    List<Blog> blogList2 = mapper.getBlogListByIdList(idList);
    System.out.println(blogList == blogList2);

    sqlSession.close();
}

输出结果

image-20210416135927440

mybatis 一级缓存的本质是把数据存在一个map中,如下图所示

image-20210416141105364

一级缓存失效条件

1.如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。

2.如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。

3.SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空缓存对象的数据,但是该对象可以继续使用

13.3 mybatis二级缓存
  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;

工作机制

  • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
  • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
  • 新的会话查询信息,就可以从二级缓存中获取内容;
  • 不同的mapper查出的数据会放在自己对应的缓存(map)中;

使用步骤

  1. 显示开启二级缓存

    这个默认也是开启的,但还是要显示配置一下,方便其他人阅读,增强代码的可读性。

    <settings>
            <setting name="cacheEnabled" value="true"/>
        </settings>
    
  2. 在要使用二级缓存的Mapper中开启

<!--在当前Mapper.xml中使用二级缓存-->
<cache />

也可以自定义参数

<!--在当前Mapper.xml中使用二级缓存-->
<cache  eviction="FIFO"
       flushInterval="60000"
       size="512"
       readOnly="true"/>

3.测试

@Test
public void test05(){
   	List<Integer> idList = new ArrayList<>();
    idList.add(1);
    idList.add(2);
    idList.add(3);

    SqlSession sqlSession = MybatisUtil.getSqlSession();
    BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
    List<Blog> blogList = mapper.getBlogListByIdList(idList);
    sqlSession.close();

    System.out.println("=======================");

    SqlSession sqlSession2 = MybatisUtil.getSqlSession();
    BlogMapper mapper2 = sqlSession2.getMapper(BlogMapper.class);
    List<Blog> blogList2 = mapper2.getBlogListByIdList(idList);
    sqlSession2.close();

    System.out.println(blogList2 == blogList);
}

输出报错:

image-20210416142855850

报错原因:Blog实体类未系列化!

image-20210416143711600

日志中只输出了一个SQL,说明第二个查询是从缓存中取的数据,但是两个集合不相同。

小结:

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

在这里插入图片描述

13.5 自定义缓存ehcache

Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存`

要在程序中使用ehcache,先要导包!

<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.1.0</version>
</dependency>

在mapper中指定使用我们的ehcache缓存实现!

<!--在当前Mapper.xml中使用二级缓存-->
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <!--
       diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
       user.home – 用户主目录
       user.dir  – 用户当前工作目录
       java.io.tmpdir – 默认临时文件路径
     -->
    <diskStore path="./tmpdir/Tmp_EhCache"/>
    <defaultCache
            eternal="false"
            maxElementsInMemory="10000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="259200"
            memoryStoreEvictionPolicy="LRU"/>
    <cache
            name="cloud_user"
            eternal="false"
            maxElementsInMemory="5000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="1800"
            memoryStoreEvictionPolicy="LRU"/>
    <!--
       defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
     -->
    <!--
      name:缓存名称。
      maxElementsInMemory:缓存最大数目
      maxElementsOnDisk:硬盘最大缓存个数。
      eternal:对象是否永久有效,一但设置了,timeout将不起作用。
      overflowToDisk:是否保存到磁盘,当系统当机时
      timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
      timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
      diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
      diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
      diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
      memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
      clearOnFlush:内存数量最大时是否清除。
      memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
      FIFO,first in first out,这个是大家最熟的,先进先出。
      LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
      LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
   -->
</ehcache>

同样的测试方法,执行结果如下图:

image-20210416150349573

也是只有一个查询SQL输出,而且两个list集合相同

posted @ 2021-04-16 15:06  随风mc  阅读(169)  评论(0)    收藏  举报