Mybatis基础

包括:MybatisMybatis-generator自动生成代码

Mybatis

注意: maven3.6与IDEA2018版本不匹配,所以使用maven3.5.2

下载地址:https://github.com/mybatis/mybatis-3/releases

1.1 什么是 MyBatis?

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

1.2 Mybatis快速入门

思路:搭建环境-->导入需要的包-->编写代码-->测试

  • 前三个在之后的程序编写中不用改动

在这里插入图片描述

1.2.1 导入依赖

使用 Maven 来构建项目,将下面的依赖代码置于 pom.xml 文件中:

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.5</version>
</dependency>

1.2.2 编写配置文件

  • 配置文件的作用: 配置连接数据库所需要的数据源(url、driver、username、password)。

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">
<!--mybatis核心配置文件-->
<configuration>
    <!--可以包含多个环境,default选择使用哪个环境-->
    <environments default="mybatis">
        <environment id="mybatis">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <!--在xml中,特殊字符需要转义&amp;相当于&-->
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&amp;
                useUnicode=true&amp;charsetEncoding=utf-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="cn/rm/dao/UserMapper.xml"/>
    </mappers>
</configuration>

1.2.3 编写MyBatis工具类

工具类的作用:

  • 加载配置文件
  • 获取执行sql的对象sqlSession
public class MybatisUtil {
  private static  SqlSessionFactory sqlSessionFactory;
    //静态代码块,类加载的同时获取到sqlSessionFactory
    static {
        InputStream  inputStream =null;
        try {
            //根据流得到sqlSessionFactory(根据工厂建造者对象获得)
            String resource = "mybatis-config.xml";
            inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            //关闭流,释放资源
            if(inputStream!=null){
                try {
                    inputStream.close();
                } catch (IOException e) {

                }
            }
        }
    }
    //创建一个获取sqlSession对象的方法(根据工厂获得)
    //sqlSession用来执行sql语句
    public static SqlSession getSqlSession(){
        SqlSession session = sqlSessionFactory.openSession();
        return session;
    }
}

1.2.4 编写Java代码

实体类

//创建实体类,对应数据库中的user表
public class User {
    private int id;
    private String name;
    private String password;

   //...
   //构造器,getset方法,toString方法
}

Dao接口(Mapper接口)

//操作数据库中user表的接口
public interface UserMapper {
    //查找所有的用户
    List<User> findAll();
}

接口实现类(UserMapper.xml)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--该配置文件相当于过去的接口实现类-->
<!--nameSpace命名空间,绑定一个指定的Mapper(过去的Dao接口)-->
<mapper namespace="cn.rm.dao.UserMapper">
    <!--id属性对应着接口实现类的方法名-->
    <select id="findAll" resultType="cn.rm.pojo.User">
    select * from user
  </select>
</mapper>

1.2.5 测试

资源导出问题

由于maven约定大于配置,之后可能遇到写的配置文件不在指定的包下,无法导出或者生效的问题。
解决方案,在pom.xml中配置

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

UserMapperTest

public class UserMapperTest {
    @Test
    public void findAll(){
        //获取sqlSession对象
        SqlSession sqlsession = MybatisUtil.getSqlSession();
        //执行sql语句
        UserMapper mapper = sqlsession.getMapper(UserMapper.class);
        List<User> users = mapper.findAll();
        for (User user : users) {
            System.out.println(user);
        }
        sqlsession.close();

    }
}

测试结果

User{id=1, name='zhangsan', password='123456'}
User{id=2, name='lisi', password='123456'}
User{id=3, name='wangwu', password='123456'}
User{id=4, name='麻子', password='123456'}
User{id=8, name='二狗', password='23654125'}

1.3 Mybatis实现增删改查

mybatis配置文件:

  • namespace:包名要和Mapper接口一致。
  • id:对应的namespace中的方法名
  • resultType:sql语句执行后的返回值
  • parameterType:参数类型

注意事项

  • 增删改业务需要提交事务

接口

//操作数据库中user表的接口
public interface UserMapper {
    //查找所有的用户
    List<User> findAll();
    //根据id查询用户
    User getUserById(int id);
    //插入一个用户,需要传递的参数是User类型
    int addUser(User user);
    //修改一个用户
    int updateUser(User user);
    //删除一个用户,只需要传入id即可
    int deleteUser(int id);
}

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">
<!--该配置文件相当于过去的接口实现类-->
<!--nameSpace命名空间,绑定一个指定的Mapper(过去的Dao接口)-->
<mapper namespace="cn.rm.dao.UserMapper">
    <!--id属性对应着接口实现类的方法名-->
    <select id="findAll" resultType="cn.rm.pojo.User">
    select * from user
  </select>
    <!--根据id查询对应的用户-->
    <select id="getUserById" resultType="cn.rm.pojo.User" parameterType="int" >
        select *from user where id = #{id}
    </select>

    <!--插入语句需要使用insert标签-->
    <!--添加一个User,pojo中的User类中的属性可以直接获取-->
    <insert id="addUser"  parameterType="cn.rm.pojo.User">
        insert into mybatis.user (id,name,password) values (#{id},#{name},#{password})
    </insert>

    <!--修改一个用户-->
    <update id="updateUser" parameterType="cn.rm.pojo.User">
        update mybatis.user set name=#{name},password=#{password} where id=#{id} ;
    </update>

    <!--删除一个用户-->
    <delete id="deleteUser" parameterType="int">
        delete from mybatis.user where id =#{id}
    </delete>
</mapper>

测试

public class UserMapperTest {
    @Test
    public void findAll(){
        //获取sqlSession对象
        SqlSession sqlsession = MybatisUtil.getSqlSession();
        //执行sql语句
        UserMapper mapper = sqlsession.getMapper(UserMapper.class);
        List<User> users = mapper.findAll();
        for (User user : users) {
            System.out.println(user);
        }
        sqlsession.close();

    }
    @Test
    public void getUserById(){
        //获取sqlSession对象
        SqlSession sqlsession = MybatisUtil.getSqlSession();
        //执行sql语句
        UserMapper mapper = sqlsession.getMapper(UserMapper.class);
        User user = mapper.getUserById(1);
        System.out.println(user);

        sqlsession.close();
    }
    @Test
    public void addUser(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = new User(7, "强子", "12345678");
        int i = mapper.addUser(user);

        if(i>0){
            System.out.println("插入成功");
        }
        //增删改需要提交事务,才能成功(执行sql的对象开启事务)
        sqlSession.commit();
        sqlSession.close();
    }
    @Test
    public void updateUser(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = new User(7, "强哥", "1111111");
        int i = mapper.updateUser(user);
        if(i>0){
            System.out.println("更新成功");
        }
        sqlSession.commit();
        sqlSession.close();
    }
    @Test
    public void deleteUser(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int i = mapper.deleteUser(5);
        if(i>0){
            System.out.println("删除成功");
        }
        sqlSession.commit();
        sqlSession.close();
    }

}

错误分析

  • 标签不要匹配错,插入语句使用insert标签
  • resource绑定mapper,需要使用路径/
  • 程序配置文件需要符合规范
  • 空指针异常,没有注册到资源
  • maven资源没有导出问题

1.3.1 万能的Map

实体类或者数据库中的表,字段或者属性过多时,有些有默认值的不需要传参,应该考虑使用Map

  • 修改用户的一个属性,不需要传递整个对象
  • sql语句中的参数对应着map中的key
  • map的value就对应着实际传递进去的参数

UserMapper

 //修改一个用户,传递Map参数
    int updateUser2(Map<String,Object> map);

UserMapper.xml

<!--修改一个用户,传递Map的key,测试的时候传递map对应key的值-->
    <update id="updateUser2" parameterType="map">
        update mybatis.user set name=#{name} where id=#{id} ;
    </update>

Test

@Test
 public void updateUser2(){
     SqlSession sqlSession = MybatisUtil.getSqlSession();
     UserMapper mapper = sqlSession.getMapper(UserMapper.class);
     HashMap<String, Object> map = new HashMap<String, Object>();
     map.put("name","诸葛青");
     map.put("id",3);
     int i = mapper.updateUser2(map);
     if(i>0){
         System.out.println("修改成功");
     }
     sqlSession.commit();
     sqlSession.close();
 }
  1. Map传递参数,直接在sql中取出key即可【parameterType="map"】
  2. 对象传递参数,直接在sql中取对象的属性即可【parameterType="Object"】
  3. 只有一个基本类型参数的情况下,在sql中可以直接获取

1.3.2 模糊查询

1. Java代码执行的时候,传递通配符% %

  @Test
    public void getLikeUser(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = mapper.getLikeUser("%强%");
        for (User user : users) {
            System.out.println(user);
        }
        sqlSession.close();
    }

2. 在sql拼接中使用通配符

<!--模糊查询-->
  <select id="getLikeUser" resultType="cn.rm.pojo.User">
      select * from mybatis.user where name like "%"#{value}"%"
  </select>

1.4 Mybatis配置解析

mybatis-config.xml 这一文件可以配置的内容。

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

在xml配置文件中,标签顺序必须符合上面的标签顺序,properties最前面,mappers最后面

1.4.1 properties

外部配置

  • 创建db.properties文件, 包含连接数据库的四个基本信息,driver,url,username,password,通过resources属性引入外部资源文件
  • 之后就可以在数据源中使用${键名}来代替具体的内容
<properties resource="db.properties"/>
  • 创建properties文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&charsetEncoding=utf-8&useUnicode=true
name=root
password=root
  • 在mybatis-config中引入外部文件
<?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">
<!--mybatis核心配置文件-->
<configuration>
    <!--引入外部properties文件-->
    <properties resource="db.properties"/>

    <environments default="mybatis">
        <environment id="mybatis">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <!--在xml中,特殊字符需要转义&amp;相当于&-->
                <property name="url" value="${url}"/>
                <property name="username" value="${name}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="cn/rm/mapper/UserMapper.xml"/>
    </mappers>
</configuration>

内部配置

  • 在properties中添加键值对信息
  • 如果两个文件有同一字段,优先使用外部配置文件的字段
<properties resource="db.properties">
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</properties>

1.4.2 settings

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

cacheEnabled

  • 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。
  • 默认true

lazyLoadingEnabled

  • 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。
  • 默认false

logImpl(日志实现)

  • 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。

  • 可选择:

SLF4J 
LOG4J 【掌握】
LOG4J2 
JDK_LOGGING 
COMMONS_LOGGING 
STDOUT_LOGGING 【掌握】
NO_LOGGING

mapUnderscoreToCamelCase

  • 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。
  • 默认false
  • 因为oracle会把字段全部转换为大写,为方便阅读,使用_分开单词,如:last_name

1.4.3 typeAliases

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。

  • 当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。
<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
  <typeAlias alias="Comment" type="domain.blog.Comment"/>
</typeAliases>
  • 也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:
<typeAliases>
     <package name="cn.rm.pojo"/>
 </typeAliases>
  • 每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 cn.rm.pojo.User 的别名为 user;若有注解,则别名为其注解值。见下面的例子:
@Alias("user")
public class User{
    ...
}

注意

  • 在实体类比较少时,使用第一种,在实体类比较多时,使用第二种
  • 如果返回值类型或者参数是基本类型,需要在前面加_,比如别名_int就表示返回值类型是int。
  • 第一种可以DIY别名,第二种如果想要改别名,需要在实体类上面添加@Alias注解,则别名为其注解值。下面的例子:
@Alias("author")
public class Author {
    ...
}

1.4.4 enviroments

  • MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中。

  • 现实情况下有多种理由需要这么做。例如,开发测试生产环境需要有不同的配置

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

<environments default="dev">

	<!--开发环境-->
    <environment id="dev">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="${driver}"/>
            <!--在xml中,特殊字符需要转义&amp;相当于&-->
            <property name="url" value="${url}"/>
            <property name="username" value="${name}"/>
            <property name="password" value="${password}"/>
        </dataSource>
    </environment>
    
    <!--测试环境-->
   <environment id="test">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="${driver}"/>
            <!--在xml中,特殊字符需要转义&amp;相当于&-->
            <property name="url" value="${url}"/>
            <property name="username" value="${name}"/>
            <property name="password" value="${password}"/>
        </dataSource>
    </environment>
    
</environments>

transacationManager(事务管理器)

  • 在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]")
  • Mybatis默认的事务管理器是JDBC,连接池:POOLED

JDBC

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

MANAGED

  • 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。

注意

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

  • 这两种事务管理器类型都不需要设置任何属性。它们其实是类型别名,换句话说,你可以用 TransactionFactory 接口实现类的全限定名或类型别名代替它们。

dataSource(数据源)

  • dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。
  • 大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。
  • 有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"):

UNPOOLED

  • 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择

POOLED

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

JNDI

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

1.4.5 plugins(插件)

  • 使用外部插件可以进一步简化开发

在maven仓库中可以查询

  • mybatis-generator-core(自动生成代码)
  • mybatis-plus(mybatis增强版)
  • 通用mapper

1.4.6 mappers

  • 告诉Mybatis去哪找SQL映射语句
  • 可以使用相对于类路径的资源引用,或完全限定资源定位符

resource·

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
</mappers>

class

  • 注意:接口和他的Mapper配置文件必须同名。
  • 接口和他的配置文件必须在同一个包下。
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
</mappers>

package

<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

1.5 作用域和生命周期

  • 不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。
    在这里插入图片描述

SqlSessionFactoryBuilder

  • 这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。
  • 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。

SqlSessionFactory

  • SqlSessionFactory相当于数据库连接池, 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
  • 因此 SqlSessionFactory 的最佳作用域是应用作用域。
  • 最简单的就是使用单例模式或者静态单例模式。

SqlSession

  • 连接到连接池的一个请求
  • sqlSession的实例不是线程安全的,因此是不能被共享的,所以它的最佳作用域是请求或者方法作用域,定义在一个方法体中。
  • 用完需要赶紧关闭,否则资源被占用

在这里插入图片描述

  • 这里面的每一个Mapper就代表一个具体的业务
  • 一个sqlSession可以有多个Mapper
@Test
public void getLikeUser(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    UserMapper mapper1 = sqlSession.getMapper(UserMapper.class);
    System.out.println(mapper.hashCode());//1924582348
    System.out.println(mapper1.hashCode());//11003494
    List<User> users1 = mapper1.findAll();
    for (User user : users1) {
        System.out.println(user);
    }
    List<User> users = mapper.getLikeUser("强");
    for (User user : users) {
        System.out.println(user);
    }
    sqlSession.close();
}

1.6 属性名和字段名不一致

  • 如果数据库中的字段名和实体类中的属性不一致的话,该字段的查询结果会为null
  • 实体类中属性名为pwd,数据库中字段为password
User{id=1, name='zhangsan', pwd='null'}

起别名

select id,name,password as pwd from mybatis.user where id = #{id};

resultMap(结果集映射)

  • resultMap 元素是 MyBatis 中最重要最强大的元素。
  • ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
id   name   password
id   name   pwd
<?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="cn.rm.mapper.UserMapper">
    <!--i对应需要映射的结果集映射,type对应的实体类-->
    <resultMap id="UserMap" type="User">
        <!--column对应数据库中的字段,property实体类中的属性-->
        <result column="password" property="pwd"/>
    </resultMap>
    
    <select id="findAll" resultMap="UserMap">
  	  select * from user
 	</select>
</mapper>

1.7 日志

1.7.1 日志工厂

  • 如果一个数据库操作,出现了异常,需要排错。这时候就需要使用日志了。

  • 以前:sout、debug。现在:日志工厂

  • 在Mybatis中具体使用哪一个日志实现,在setting标签中设置

  • STDOUT_LOGGING: 标准日志输出

在settings中设置日志模式

<!--使用日志工厂-->
 <settings>
     <setting name="logImpl" value="STDOUT_LOGGING"/>
 </settings>

控制台输出结果

Opening JDBC Connection
Created connection 1843289228.
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6dde5c8c]
==>  Preparing: select * from user where id = ? 
==> Parameters: 1(Integer)
<==    Columns: id, name, password
<==        Row: 1, zhangsan, 123456
<==      Total: 1
User{id=1, name='zhangsan', password='123456'}
Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@6dde5c8c]
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@6dde5c8c]
Returned connection 1843289228 to pool.

1.8 Log4j

1.8.1 什么是Log4j?

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

1.8.2 快速使用

  1. 导包
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
  1. 编写配置文件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/rm.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的日志实现
<settings>
    <setting name="logImpl" value="LOG4J"/>
</settings>
  1. log4j使用结果
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Opening JDBC Connection
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 824208363.
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@31206beb]
[cn.rm.mapper.UserMapper.getUserById]-==>  Preparing: select * from user where id = ? 
[cn.rm.mapper.UserMapper.getUserById]-==> Parameters: 1(Integer)
[cn.rm.mapper.UserMapper.getUserById]-<==      Total: 1
User{id=1, name='zhangsan', password='123456'}
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@31206beb]
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@31206beb]
[org.apache.ibatis.datasource.pooled.PooledDataSource]-Returned connection 824208363 to pool.

1.8.3 自定义使用Log4j

  1. 在需要使用log4j的类中导入包
import org.apache.log4j.Logger;
  1. 生成日志对象,加载参数为当前类的class
Logger logger = Logger.getLogger(UserMapperTest.class);
  1. 使用日志对象
public class UserMapperTest {
    Logger logger = Logger.getLogger(UserMapperTest.class);
    @Test
    public void testLog4j(){
        logger.info("info:进入了testLog4j方法");
        logger.debug("debug:进入了testLog4j方法");
        logger.error("error:进入了testLog4j方法");
    }
}
  1. 查看输出结果
[cn.rm.mapper.UserMapperTest]-info:进入了testLog4j方法
[cn.rm.mapper.UserMapperTest]-debug:进入了testLog4j方法
[cn.rm.mapper.UserMapperTest]-error:进入了testLog4j方法
  • 生成的日志文件中的结果

在这里插入图片描述

1.9 分页

  • 分页为了减少数据的处理量

1.9.1 使用limit分页

select * from user limit startIndex,PageSize;
select * from user limit 3; #[0,3)
  1. 编写Mapper
//实现分页查询功能
List<User> selectUserByLimit(Map<String,Integer> map);
  1. 编写对应的xml文件
<!--分页查询功能-->
<select id="selectUserByLimit" resultType="user" parameterType="map">
    select * from mybatis.user limit #{startIndex},#{pageSize}
</select>
  1. 测试代码
@Test
 public void selectUserByLimit(){
     SqlSession sqlSession = MybatisUtil.getSqlSession();
     UserMapper mapper = sqlSession.getMapper(UserMapper.class);
     HashMap<String, Integer> map = new HashMap<String, Integer>();
     map.put("startIndex",1);
     map.put("pageSize",3);
     List<User> users = mapper.selectUserByLimit(map);
     for (User user : users) {
         System.out.println(user);
     }
     sqlSession.close();
 }
  1. 输出结果
[cn.rm.mapper.UserMapper.selectUserByLimit]-==>  Preparing: select * from mybatis.user limit ?,? 
[cn.rm.mapper.UserMapper.selectUserByLimit]-==> Parameters: 1(Integer), 3(Integer)
[cn.rm.mapper.UserMapper.selectUserByLimit]-<==      Total: 3
User{id=2, name='lisi', password='123456'}
User{id=3, name='诸葛青', password='111'}
User{id=4, name='麻子', password='123456'}

1.9.2 RowBounds(不建议使用)

  1. 编写Mapper
//实现分页查询功能
List<User> getUserByRowBounds();
  1. 编写对应的xml文件
<!--java代码实现分页查询-->
<select id="getUserByRowBounds" resultType="user">
    select * from user;
</select>
  1. 测试代码
@Test
public void selectByRowBounds(){
    //通过java代码实现分页查询
    SqlSession sqlSession = MybatisUtil.getSqlSession();

    RowBounds rowBounds = new RowBounds(1, 2);
    List<User> userList = sqlSession.selectList("cn.rm.dao.UserMapper.getUserByRowBounds", null, rowBounds);
    for (User user : userList) {
        System.out.println(user);
    }
    sqlSession.close();
}

1.9.3 pageHelper插件

在这里插入图片描述

1.10 使用注解开发

1.10.1 面向接口编程

  • 大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程

  • 根本原因 : 解耦 , 可拓展 , 提高复用 , 分层开发中 , 上层不用管具体的实现 , 大家都遵守共同的标准 , 使得开发变得容易 , 规范性更好

  • 在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;

  • 而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

关于接口的理解

  • 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。

  • 接口的本身反映了系统设计人员对系统的抽象理解。
    接口应有两类:

  • 第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);

  • 第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface);

  • 一个体有可能有多个抽象面。抽象体与抽象面是有区别的。

三个面向区别

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

1.10.2 使用注解开发

  1. 注解在接口上实现
@Select("select * from user where id = #{id}")
//根据id查询用户
User getUserById(int id);
  1. 在mybatis-config中绑定接口
<!--绑定接口-->
<mappers>
    <mapper class="cn.rm.dao.UserMapper"/>
</mappers>
  1. 测试,查看结果

public class UserMapperTest {
    @Test
    public void getUsers(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = mapper.getUsers();
        for (User user : users) {
            System.out.println(user);
        }
        sqlSession.close();
    }
}
  1. 使用debug分析

在这里插入图片描述
注解本质使用反射机制,底层使用动态代理

UserMapper mapper = sqlSession.getMapper(UserMapper.class);
  • 该语句可以获得UserMapper的所有东西

Mybatis详细的执行流程
在这里插入图片描述

1.10.3 使用注解完成CRUD

只需要修改UserMapper和测试类即可

  • UserMapper
public interface UserMapper {
    //查询所有
    @Select("select * from user")
    List<User> getUsers();

    //增加一个用户
    @Insert("insert into user values(#{id},#{name},#{password})")
    int addUser(User user);

    //根据id删除一个用户
    @Delete("delete from user where id =#{id}")
    int deleteUser(int id);

    //根据id更新一个用户的信息
    @Update("update user set password =#{password} where id=#{id}")
    int updateUser(Map<String,Object> map);

}
  • Test
public class UserMapperTest {
    @Test
    public void getUsers(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = mapper.getUsers();
        for (User user : users) {
            System.out.println(user);
        }
        sqlSession.close();
    }

    @Test
    public void addUser(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int i = mapper.addUser(new User(10, "嘤嘤嘤", "5555555"));
        if(i>0){
            System.out.println("插入成功");
        }
        sqlSession.commit();
        sqlSession.close();
    }

    @Test
    public void deleteUser(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        int i = mapper.deleteUser(10);
        if(i>0){
            System.out.println("删除成功");
        }
        sqlSession.commit();
        sqlSession.close();
    }

    @Test
    public void updateUser(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        HashMap map = new HashMap();
        map.put("password","8888888");
        map.put("id",9);
        int i = mapper.updateUser(map);
        if(i>0){
            System.out.println("修改成功");
        }
        sqlSession.commit();
        sqlSession.close();
    }
}

#{}和${}的区别

{}带预编译功能,预防sql注入

${}不带预编译

#{}

//根据id查用户
    @Select("select * from user where id=#{id}")
    User getUserById(int id);

结果

==>  Preparing: select * from user where id=? 
==> Parameters: 5(Integer)

${}

 //根据id查用户
    @Select("select * from user where id=${id}")
    User getUserById(int id);

结果

==>  Preparing: select * from user where id=5 
==> Parameters: 

@param()

  • 基本类型的参数或者String类型,需要加上
  • 引用类型不用加
  • 如果参数只有一个基本类型的话,可以忽略
  • 在SQL中引用的就是该注解中的属性名
//根据id查用户,传入的参数就是@Param中的值
    @Select("select * from user where id=${idddddd}")
    User getUserById(@Param("idddddd") int id);

设置自动提交事务

  • 在创建sqlSession的时候就默认自动提交事务
  • 开启Sesson的时候传递一个true参数即可
//获取sqlSession
public static SqlSession getSqlSession(){
    return sqlSessionFactory.openSession(true);
}

在这里插入图片描述

1.11 Lombok

  • 一款注解插件,只需在实体类上面添加该注解,不用再写getset方法

安装插件:

在这里插入图片描述
包含的注解:

@Getter and @Setter
@FieldNameConstants
@ToString
@EqualsAndHashCode
@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
@val
@var
experimental @var
@UtilityClass

使用步骤:

1. 在IDEA中安装Lombok插件
2. 在项目中导入lombokjar包
3. 在实体类中加注解
  • 添加lombok依赖
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.12</version>
    <scope>provided</scope>
</dependency>

说明

@Data:无参构造、get、set、toString、hashcode、equals
@AllArgsConstructor:全参构造,加入该注解后,@Data生成的无参构造就会消失
@NoArgsConstructor:无参构造

1.12 多对一的处理

  • 多个学生,对应一个老师
  • 对于学生这边来说:关联...多个学生关联一个老师(association)【多对一】
  • 对于老师而言:集合...一个老师拥有多个学生(collection)【一对多】

teacher表

CREATE TABLE `teacher` (
 `id` INT(10) NOT NULL,
 `name` VARCHAR(30) DEFAULT NULL,
 PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8

student表

CREATE TABLE `student` (
 `id` INT(10) NOT NULL AUTO_INCREMENT,
 `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 AUTO_INCREMENT=6 DEFAULT CHARSET=utf8
  • 通过物理外键实现多对一
    在这里插入图片描述

1.12.1 多对一处理

  • 导入项目需要的依赖
mysql-connector-java
junit
mybatis
lombok
  • 编写核心配置文件mybatis-config.xml

  • 编写工具类MybatisUtil

  • 编写实体类Student和Teacher

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
   private int id;
   private String name;
   //学生需要关联一个老师
   private Teacher teacher;
}
  • 编写对应的Mapper
  • 将mapper注册到mybatis-config.xml中
<!--注册mappers-->
<mappers>
    <mapper class="cn.rm.dao.TeacherMapper"/>
    <mapper class="cn.rm.dao.StudentMapper"/>
</mappers>
  • 测试

查询所有的学生信息,以及对应的老师的信息

select s.id,s.NAME,t.name from student s, teacher t where s.tid = t.id

在这里插入图片描述

按照查询嵌套处理

StudentMapper

public interface StudentMapper {
    List<Student> getStudents();
}

StudentMapper.xml

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

<!--namespace绑定接口-->
<mapper namespace="cn.rm.dao.StudentMapper">

    <!--查询所有的学生信息-->
    <select id="getStudents" resultMap="studentTeacher">
        select * from student
    </select>

    <!--resultMap-->
    <resultMap id="studentTeacher" type="student">
        <result column="id" property="id"/>
        <result column="name" property="name"/>
        <!--实体类中是对象,数据库中的tid字段,要使这两个对应上-->
        <!--复杂的属性,需要单独处理,实体类中的对象属性:association 集合:collection-->
        <association property="teacher" column="tid" javaType="teacher" select="getTeacher"/>
    </resultMap>
    
    <select id="getTeacher" resultType="teacher">
        select * from mybatis.teacher where id=#{id}
    </select>

</mapper>

测试结果

==>  Preparing: select * from student 
==> Parameters: 
<==    Columns: id, NAME, tid
<==        Row: 1, 小明, 1
====>  Preparing: select * from mybatis.teacher where id=? 
====> Parameters: 1(Integer)
<====    Columns: id, name
<====        Row: 1, 王老师
<====      Total: 1
<==        Row: 2, 小红, 1
<==        Row: 3, 小蓝, 1
<==        Row: 4, 小王, 1
<==        Row: 5, 小李, 1
<==      Total: 5
Student(id=1, name=小明, teacher=Teacher(id=1, name=王老师))
Student(id=2, name=小红, teacher=Teacher(id=1, name=王老师))
Student(id=3, name=小蓝, teacher=Teacher(id=1, name=王老师))
Student(id=4, name=小王, teacher=Teacher(id=1, name=王老师))
Student(id=5, name=小李, teacher=Teacher(id=1, name=王老师))

按照结果嵌套处理

StudentMapper.xml

<select id="getStudents2" resultMap="studentTeacher2">
        select s.id sid,s.name sname,t.name tname
        from student s, teacher t
        where s.tid = t.id
    </select>

    <resultMap id="studentTeacher2" type="student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <association property="teacher" javaType="teacher">
            <!--teacher表中的对应字段-->
            <result property="name" column="tname"/>
        </association>
    </resultMap>

测试结果

==>  Preparing: select s.id sid,s.name sname,t.name tname from student s, teacher t where s.tid = t.id 
==> Parameters: 
<==    Columns: sid, sname, tname
<==        Row: 1, 小明, 王老师
<==        Row: 2, 小红, 王老师
<==        Row: 3, 小蓝, 王老师
<==        Row: 4, 小王, 王老师
<==        Row: 5, 小李, 王老师
<==      Total: 5
Student(id=1, name=小明, teacher=Teacher(id=0, name=王老师))
Student(id=2, name=小红, teacher=Teacher(id=0, name=王老师))
Student(id=3, name=小蓝, teacher=Teacher(id=0, name=王老师))
Student(id=4, name=小王, teacher=Teacher(id=0, name=王老师))
Student(id=5, name=小李, teacher=Teacher(id=0, name=王老师))

回顾Mysql多对一查询方式

  • 子查询【select id,NAME,tid from student where tid=(select id from teacher where student.tid=teacher.id)】
  • 联表查询【select s.id,s.NAME,t.name from student s, teacher t where s.tid = t.id;】

1.12.2 一对多处理

  • 一个老师拥有多个学生
  • 对老师而言,就是一对多的关系
  1. 环境搭建

实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
    private int id;
    private String name;
    //一个老师拥有多个学生
    private List<Student> students;
}
public class Student {
    private int id;
    private String name;
    private int tid;
}

1.12.3 按照结果嵌套处理

<!--1.按结果嵌套查询-->
<select id="getTeacher" resultMap="TeacherStudent">
SELECT t.id tid, t.name tname,s.id sid ,s.name sname
FROM student s,teacher t
where s.tid=t.id
and t.id =#{id}
</select>

<resultMap id="TeacherStudent" 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>

测试结果

/*
==>  Preparing: SELECT t.id tid, t.name tname,s.id sid ,s.name sname FROM student s,teacher t where s.tid=t.id and t.id =? 
==> Parameters: 1(Integer)
<==    Columns: tid, tname, sid, sname
<==        Row: 1, 王老师, 1, 小明
<==        Row: 1, 王老师, 2, 小红
<==        Row: 1, 王老师, 3, 小蓝
<==        Row: 1, 王老师, 4, 小王
<==        Row: 1, 王老师, 5, 小李
<==      Total: 5
Teacher(id=1, 
name=王老师, 
students=[Student(id=1, name=小明, tid=1),
Student(id=2, name=小红, tid=1),
Student(id=3, name=小蓝, tid=1),
Student(id=4, name=小王, tid=1),
Student(id=5, name=小李, tid=1)])
*/

1.12.4 小结

  1. 关联:association【多对一】

  2. 集合:collection 【一对多】

  3. JavaType & ofType

注意点

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

面试高频

  • Mysql引擎
  • InnoDB底层原理
  • 索引
  • 索引优化

1.13 动态sql

概念:

  • 动态sql就是根据不同的需求生成不同的sql语句
if
choose(when,otherwise)
trim(where,set)
foreach

1.13.1 搭建环境

  • mybatis-config.xml
  • 工具类MybatisUtil、IDUtil
  • 实体类Blog
  • dao接口BlogMapper
  • 对应的BlogMapper.xml
  • 测试

IDUtil工具类

  • 生成一个随机的ID
public class IDUtil {
    public static String getID(){
        //返回一个随机生成的id
        String s = UUID.randomUUID().toString();
        //将-替换为空(7949cc75-b278-4db7-93f1-5b4e6eb6cf25)
       return s.replace("-","");
    }

    @Test
    public void test(){
        System.out.println(IDUtil.getID());
    }
    //测试结果 88c4c6ec951d46eb809984cb6ee2758c
}

数据库字段和实体类属性不一致

  • 在setting中将自动驼峰转换打开
<!--使用日志工厂,开启自动驼峰转换-->
<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

1.13.2 if标签

  • sql语句后面添加where1=1是为了第一个条件不成立的时候拼接第二个and语句
<!--如果不传递参数就查询所有,传递参数就根据参数进行sql的拼接-->
<select id="selectBlogIf" resultType="blog" parameterType="map">
    select * from mybatis.blog where 1=1
    <if test="title != null">
        and title = #{title}
    </if>
    <if test="author != null">
        and author=#{author}
    </if>
</select>
  • 测试
@Test
public void selectBlogIf() {
    BlogMapper mapper = MybatisUtil.getSqlSession().getMapper(BlogMapper.class);
    HashMap map = new HashMap();
    map.put("title", "微服务_你好");
    map.put("author", "rm");
    List<Blog> blogs = mapper.selectBlogIf(map);
    for (Blog blog : blogs) {
        System.out.println(blog);
    }
}
  • 结果
==>  Preparing: select * from mybatis.blog where 1=1 and title = ? and author=? 
==> Parameters: 微服务_你好(String), rm(String)
<==    Columns: id, title, author, create_time, views
<==        Row: 8de626bb226d48cbb4b492578de26d7e, 微服务_你好, rm, 2020-08-25 22:28:00.0, 600
<==      Total: 1
Blog(id=8de626bb226d48cbb4b492578de26d7e, title=微服务_你好, author=rm, createTime=Tue Aug 25 22:28:00 CST 2020, views=600)

1.13.3 choose、when、otherwise标签

  • 有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
<select id="selectBlogChoose" resultType="blog" parameterType="map">
    select * from mybatis.blog
    <where>
        <choose>
            <when test="title !=null">
                and title = #{title}
            </when>
            <when test="author != null">
                and author = #{author}
            </when>
            <otherwise>

            </otherwise>
        </choose>
    </where>
</select>
  • where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

  • 如果两个条件都成立,只会拼接上面的语句

 @Test
    public void selectBlogChoose() {
        BlogMapper mapper = MybatisUtil.getSqlSession().getMapper(BlogMapper.class);
        HashMap map = new HashMap();
        map.put("title", "微服务_你好");
        map.put("author", "rm");
        List<Blog> blogs = mapper.selectBlogChoose(map);
        for (Blog blog : blogs) {
            System.out.println(blog);
        }
    }

结果

==>  Preparing: select * from mybatis.blog WHERE title = ? 
==> Parameters: 微服务_你好(String)
<==    Columns: id, title, author, create_time, views
<==        Row: 8de626bb226d48cbb4b492578de26d7e, 微服务_你好, rm, 2020-08-25 22:28:00.0, 600
<==        Row: a3f0cb676ad6491c89a01789834d3417, 微服务_你好, rm2, 2020-08-30 21:56:11.0, 500
<==      Total: 2
Blog(id=8de626bb226d48cbb4b492578de26d7e, title=微服务_你好, author=rm, createTime=Tue Aug 25 22:28:00 CST 2020, views=600)
Blog(id=a3f0cb676ad6491c89a01789834d3417, title=微服务_你好, author=rm2, createTime=Sun Aug 30 21:56:11 CST 2020, views=500)

1.13.4 trim、where、set标签

set

  • set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
<update id="updateBlog" parameterType="map">
   update mybatis.blog
   <set >
     <if test="title != null">
          title=#{title},
     </if>
       <if test="author != null">
            author=#{author},
       </if>
       <if test="create_time != null">
            create_time=#{createTime},
       </if>
       <if test="views != null">
            views=#{views}
       </if>
   </set>
   where id = #{id}
</update>
  • 测试
@Test
public void updateBlog() {
    BlogMapper mapper = MybatisUtil.getSqlSession().getMapper(BlogMapper.class);
    HashMap map = new HashMap();
    map.put("title", "微服务我我我我我我你好");
    map.put("author", "xxxxxx");
    map.put("id","a3f0cb676ad6491c89a01789834d3417");
    int i = mapper.updateBlog(map);
    if (i>0){
        System.out.println("修改成功");
    }
}

结果

==>  Preparing: update mybatis.blog SET title=?, author=? where id = ? 
==> Parameters: 微服务我我我我我我你好(String), xxxxxx(String), a3f0cb676ad6491c89a01789834d3417(String)
<==    Updates: 1
修改成功

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

1.13.5 sql标签

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

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

在需要引用该片段的地方使用include引用

<select id="selectBlogIf" resultType="blog" parameterType="map">
    select * from mybatis.blog 
    <where>
    <include refid="title-author"/>
     <where/>
</select>

注意事项

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

1.13.6 foreach标签

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

在这里插入图片描述

  • 想要获取id为1和3的信息
  • 使用sql语句查询
select * from mybatis.blog where (id=1 or id=3)

通过动态sql实现

<!--通过map传递一个集合select * from mybatis.blog where (id=1 or id=3)-->
<select id="selectBlogForeach" resultType="blog" parameterType="map">
    select * from mybatis.blog
    <where>
        <foreach collection="ids" item="id" open="(" separator="or" close=")">
            id = #{id}
        </foreach>
    </where>
</select>

注意事项

  • foreach标签中的ids是map中传递的ids的值
  • item表示的是从ids集合中取得的每一个元素,用做下面的为id赋值
    测试
@Test
public void selectBlogForeach(){
    BlogMapper mapper = MybatisUtil.getSqlSession().getMapper(BlogMapper.class);
    HashMap map = new HashMap();
    ArrayList<Integer> ids = new ArrayList<Integer>();
    ids.add(1);
    ids.add(3);
    System.out.println(ids);
    map.put("ids",ids);
    List<Blog> blogs = mapper.selectBlogForeach(map);
    for (Blog blog : blogs) {
        System.out.println(blog);
    }
}

1.14 缓存

每次仅限查询都会连接数据库,消耗资源
一次查询的结果,将它暂时存放到一个可以直接取到的地方(内存)
再次查询该数据的时候就可以直接到内存中取,而不用再连接数据库了

1.14.1 什么是缓存?

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

1.14.2 为什么使用缓存?

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

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

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

1.15 Mybatis缓存

 MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。

MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存

- 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)	
- 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
- 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存
import org.apache.ibatis.cache.Cache;
public interface Cache {
    String getId();

    void putObject(Object var1, Object var2);

    Object getObject(Object var1);

    Object removeObject(Object var1);

    void clear();

    int getSize();

    default ReadWriteLock getReadWriteLock() {
        return null;
    }
}

1.15.1 一级缓存

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

测试步骤

  1. 开启日志
<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
  1. 测试在一个session中查询两次相同记录
@Test
 public void getUserById(){
     SqlSession sqlSession = MybatisUtil.getSqlSession();
     UserMapper mapper = sqlSession.getMapper(UserMapper.class);
     User user = mapper.getUserById(1);
     System.out.println(user);
     System.out.println("=============");
     User user1 = mapper.getUserById(1);
     System.out.println(user1);
     System.out.println(user==user1);
     sqlSession.close();
 }

测试结果

Opening JDBC Connection
Created connection 22805895.
==>  Preparing: select * from mybatis.user where id =? 
==> Parameters: 1(Integer)
<==    Columns: id, name, password
<==        Row: 1, zhangsan, 123456
<==      Total: 1
User(id=1, name=zhangsan, password=123456)
=============
User(id=1, name=zhangsan, password=123456)
true
Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@15bfd87]
Returned connection 22805895 to pool.

缓存失效的情况

  • 查询不同的内容
  • 增删改操作,可能会改变原来的数据,会刷新缓存
public class MyTest {
    @Test
    public void getUserById(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.getUserById(1);
        System.out.println(user);
        HashMap map = new HashMap();
        map.put("name","hhhhh");
        map.put("id",2);
        mapper.updateUser(map);
        System.out.println("=============");
        User user1 = mapper.getUserById(1);
        System.out.println(user1);
        System.out.println(user==user1);
        sqlSession.close();
    }
}

结果

在这里插入图片描述

  • 查询不同的Mapper.xml
  • 手动清除缓存
public class MyTest {
    @Test
    public void getUserById(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.getUserById(1);
        sqlSession.clearCache();
        System.out.println("=============");
        User user1 = mapper.getUserById(1);
        sqlSession.close();
    }
}

结果

在这里插入图片描述

小结

  • 一级缓存默认开启,只在一次sqlSession中有效,也就是拿到连接到关闭连接这个区间
  • 一级缓存就是一个Map

在这里插入图片描述

1.15.2 二级缓存

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

步骤

  1. 开启全局缓存
<!--显式的开启全局缓存-->
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>
  1. 在需要开启缓存的mapper.xml中开启二级缓存(可以自定义一些参数)
<!--在当前mapper.xml中使用二级缓存-->
<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

测试

public class MyTest {
    @Test
    public void getUserById(){
        SqlSession sqlSession = MybatisUtil.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.getUserById(1);
        sqlSession.close();
        //一级缓存关闭后,将数据交给二级缓存
        SqlSession sqlSession2 = MybatisUtil.getSqlSession();
        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
        User user2 = mapper2.getUserById(1);
        System.out.println(user==user2);
        sqlSession2.close();
    }
}

测试结果

  • 二级缓存生效,只查询一次
    在这里插入图片描述
    小结
  • 只要开启了二级缓存,在同一个Mapper下就有效
  • 所有的数据都会先放在一级缓存中
  • 只有当会话提交或者关闭的时候,数据才会从一级缓存提取到二级缓存中。

1.15.3 缓存原理

在这里插入图片描述

MyBatis-Generator

MyBatis Generator 是 MyBatis 官方提供的代码生成器插件,可以用于 MyBatis 和 iBatis 框架的代码生成,支持所有版本的 MyBatis 框架以及 2.2.0 版本及以上的 iBatis 框架。

  • 在进行功能开发时,一张表我们需要编写实体类、DAO 接口类以及 Mapper 文件,这些是必不可少的文件,如果表的数量较大,我们就需要重复的去创建实体类、Mapper 文件以及 DAO 类并且需要配置它们之间的依赖关系,这无疑是一个很麻烦的事情。
  • MyBatis Generator 插件就可以帮助我们去完成这些开发步骤,使用该插件可以帮助开发者自动去创建实体类、Mapper 文件以及 DAO 类并且配置好它们之间的依赖关系,我们可以直接在项目中使用这些代码,后续只需要关注业务方法的开发即可。

2.1 什么是MyBatis-Generator

通过介绍我们可以将 MyBatis-Generator 简单的理解为一个 MyBatis 框架的代码生成器,至于我推荐大家使用它的原因,理由整理如下:

  • 减少重复工作
  • 减少人为操作带来的错误
  • 提升开发效率
  • 使用灵活

MyBatis 属于半自动 ORM 框架,在使用这个框架中,工作量最大的就是书写 Mapper 及相关映射文件,同时需要配置其依赖关系,由于手动书写很容易出错,我们可以利用 MyBatis-Generator 来帮我们自动生成文件。

其次,一个项目通常有很多张表,比如接下来要开发的 my_blog 项目,所有的实体类、SQL 语句都要手动编写,这是一个比较繁琐的过程,如果有这么一个插件能够适当的减少我们的一些工作量,自动将这些代码生成到对应的项目目录中,将是一件十分幸福的事情,当然,该插件生成的代码都是常用的一些增删改查代码,如果有其他功能或方法依然需要自己去编写代码,它只是一个提升效率的工具,给予开发者一定程度的帮助。

2.2 MyBatis-Generator 整合

2.2.1 添加依赖

  • 如果要使用该插件,首先我们需要将其依赖配置增加到 pom.xml 文件中,增加的配置文件如下,将该部分配置放到原 pom.xml 文件的 plugins 节点下即可:
 <plugin>
        <groupId>org.mybatis.generator</groupId>
        <artifactId>mybatis-generator-maven-plugin</artifactId>
        <version>1.3.5</version>
        <dependencies>
            <dependency>
                <groupId> mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version> 5.1.39</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-core</artifactId>
                <version>1.3.5</version>
            </dependency>
        </dependencies>
        <executions>
            <execution>
                <id>Generate MyBatis Artifacts</id>
                <phase>package</phase>
                <goals>
                    <goal>generate</goal>
                </goals>
            </execution>
        </executions>
        <configuration>
            <verbose>true</verbose>
            <!-- 是否覆盖 -->
            <overwrite>true</overwrite>
            <!-- MybatisGenerator的配置文件位置 -->
            <configurationFile>src/main/resources/mybatisGeneratorConfig.xml</configurationFile>
        </configuration>
    </plugin>

2.2.2新增 MyBatis-Generator 的配置文件

  • 在添加插件依赖到 pom.xml 文件中时,我们定义了 Mybatis Generator 的配置文件位置为 src/main/resources/mybatisGeneratorConfig.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="my-blog-generator-config" targetRuntime="MyBatis3">
        <!-- 生成的Java文件的编码 -->
        <property name="javaFileEncoding" value="utf-8"/>
        <!-- 格式化java代码 -->
        <property name="javaFormatter" value="org.mybatis.generator.api.dom.DefaultJavaFormatter"/>
        <!-- 格式化XML代码 -->
        <property name="xmlFormatter" value="org.mybatis.generator.api.dom.DefaultXmlFormatter"/>
        <plugin type="org.mybatis.generator.plugins.ToStringPlugin"/>
        <!--创建Java类时对注释进行控制-->
        <commentGenerator>
            <property name="suppressDate" value="true"/>
            <!-- 是否去除自动生成的注释 true:是 : false:否 -->
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>
        <!--数据库地址及登陆账号密码 改成你自己的配置-->
        <jdbcConnection
                driverClass="com.mysql.jdbc.Driver"
                connectionURL="jdbc:mysql://localhost:3306/my_blog_db"
                userId="root"
                password="root">
        </jdbcConnection>
        <javaTypeResolver>
            <property name="forceBigDecimals" value="false"/>
        </javaTypeResolver>
        <!--生成实体类设置-->
        <javaModelGenerator targetPackage="com.rm.pojo" targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>
        <!--生成Mapper文件设置-->
        <sqlMapGenerator targetPackage="mapper" targetProject="src/main/resources">
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>
        <!--生成Dao类设置-->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.rm.dao"
                             targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>
        <!--需要自动生成代码的表及对应的类名设置-->
        <table tableName="generator_test" domainObjectName="GeneratorTest"
               enableCountByExample="false"
               enableUpdateByExample="false"
               enableDeleteByExample="false" enableSelectByExample="false" selectByExampleQueryId="false">
        </table>
    </context>
</generatorConfiguration>

需要自动生成代码的表及对应类名的配置是写在 table 标签中的,如上所示,generator_test 表对应的实体类可以配置为 GeneratorTest,这些是开发者自定义的,也可以改成其他合适的类名。 如果有多张表同时生成,增加多个 table 标签配置即可。

2.2.3 生成代码

在这里插入图片描述

2.2.4 将 DAO 接口注册至 IOC 容器

  • 接下来是最后一个步骤,在代码生成后,我们需要在 DAO 接口类上增加 @Mapper 注解,将其注册到 Spring 的 IOC 容器中以供其他类调用,在生成的文件中默认是没有这个注解的,或者是在主类中添加 @MapperScan 注解将相应包下的所有 Mapper 接口扫描到容器中。

  • 需要导入springboot整合mybatis依赖

@SpringBootApplication
@MapperScan("com.rm.dao")
public class GeneratorApplication {

    public static void main(String[] args) {
        SpringApplication.run(GeneratorApplication.class, args);
    }

}

2.3总结

至此,MyBatis-Generator 插件整合完成,也生成了对应的代码,接下来在项目开发时,我们都会使用它来生成我们 DAO 层的代码,之后再根据功能对 Mapper 文件进行修改,使用该插件时的注意事项如下:

由于数据库连接信息是单独定义在配置文件中的,所以在使用时一定要仔细检查,确保数据库连接正常。

在生成代码后,建议将 table 标签注释掉,不然在打包或者生成其他表的代码时会将原先已经生成的代码覆盖,可能会造成一些小麻烦。

代码生成后将 DAO 接口注册至 IOC 容器以供业务方法调用。

posted @ 2021-11-04 15:46  初夏那片海  阅读(48)  评论(0)    收藏  举报