Mybatis

(数据库使用的是mysql)

一:什么是Mybatis?

  ·Mybatis是一款优秀的**持久层框架**
   
  ·Mybatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集的过程

  ·Mybatis可以使用简单的XML或注解来配置映射原生信息,将接口和java的实体类(Plain Old Java Object,普通的java对象)映射成数据库中的记录

持久化

  **持久化是将程序数据在持久状态和瞬时状态间转换的机制**

  ·即把数据(内存中的对象)保存到可永久保存的存储设备中(如磁盘)。持久化的主要应用是将内

      存中的对象存储在数据库中,或存储在磁盘文件中,XML数据文件中等等

  ·JDBC就是一种持久化机制。文件IO也是一种持久化机制。

为什么须要持久化服务呢?这是由于内存本身的缺陷引起的

  ·内存断电后数据会丢失,但有一些对象是无论如何都不能丢失的(如银行账号)

  ·内存过于昂贵,与硬盘,光盘等外存相比价格要高2~3个数量级,而且维护成本也高,至少需要一直供电,所以

        即使对象不需要永久保存,也会因为内存的容量限制不能一直保存在内存中,需要持久化来缓存到外存。

什么是持久层??

  ·完成持久化工作的代码块-->dao层(DAO Data Access Object)

  ·大多数情况下:数据持久化往往也意味着将内存中的数据保存到磁盘上加以固话,而持久化的实现过程则大

        多通过各种**关系**来完成

  ·对于系统而言,数据持久功能大多是必不可少的组成部分,也就是说,我们的系统中,已经具备了“持久层”
        概念??也许是,但也许实际情况并非如此。之所以要独立出一个“持久层”的概念,而不是“持久模

        块”,“持久单元”,也就意味着我们的系统架构中,应该有一个相对独立的逻辑层面,专注于数据持久化逻辑的实现。

  ·与系统其他部分相对而言,这个层面应该具有一个较为清晰和严格的逻辑边界。

为什么须要Mybatis??

  ·Mybatis就是帮助开发者将数据存入数据库中和从数据库中取数据。

  ·传统的jdbc操作,有很多重复代码块,如:数据取出时的封装,数据库的建立连接等等...通过框架可以减少重复代码,提高开发效率!

  ·Mybatis是一个半自动化的**ORM(Object Relationship Mapping)对象关系映射**

  ·(所有的事情,不用Mybatis依旧可以做到,只要使用了它,所有实现会更加简单!)

Mybatis优点

  ·简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件就可以了,易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。

  ·灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。

  ·解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。

  ·提供xml标签,支持编写动态sql。

  ......

MYbatis第一个程序

思路流程:搭建环境-->导入Mybatis-->编写代码-->测试

1.搭建数据库

  CREATE DATABASE mybatis;
  USE mybatis;
  DROP TABLE IF EXISTS user;
  CREATE TABLE user (
  id int(20) NOT NULL,
  name varchar(30) DEFAULT NULL,      
  pwd varchar(30) DEFAULT NULL,
  PRIMARY KEY (id)
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  insert  into user(id,name,pwd) values (1,'木易','123456'),(2,'张三','abcdef'),(3,'李四','987654');

2.导入Mybatis相关jar包

  <dependency>

  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>3.5.2</version>
  </dependency>
  <dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.47</version>

  </dependency>

3.编写Mybatis核心配置文件

  <?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.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="123456"/>
       </dataSource>
   </environment>

4.编写Mybatis工具类

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

  public class MybatisUtils {

     private static SqlSessionFactory sqlSessionFactory;

     static {
         try {
             String resource = "mybatis-config.xml";
             InputStream inputStream = Resources.getResourceAsStream(resource);
             sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
             e.printStackTrace();
        }
    }

     //获取SqlSession连接
     public static SqlSession getSession(){
         return sqlSessionFactory.openSession();
    }

  }

5.创建实体类

  public class User {
  private int id;  //id
  private String name;   //姓名
  private String pwd;   //密码

  //构造,有参,无参(此处使用lombok注解)
  //set/get
  //toString() 
}

6.编写Mapper接口类

  import com.kuang.pojo.User;
  import java.util.List;

  public interface UserMapper {
     List<User> selectUser();
  }

7.编写Mapper.xml配置文件 (namespace非常重要)

  <?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.mimiluyo.dao.UserMapper">
   <select id="selectUser" resultType="com.mimiluyo.pojo.User">
    select * from user
   </select>
  </mapper>

8.编写测试类

  public class MyTest {
     @Test
  public void selectUser() {
     SqlSession session = MybatisUtils.getSession();
     //方法一:
     //List<User> users = session.selectList("com.mimiluyo.mapper.UserMapper.selectUser");
      //方法二:
     UserMapper mapper = session.getMapper(UserMapper.class);
     List<User> users = mapper.selectUser();

     for (User user: users){
         System.out.println(user);
       }
     session.close();
     }
  }

可能出现问题:Maven静态资源过滤问题

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

二,CRUD操作及配置解析

1.UserMapper接口名改为UserDao

2.UserMapper.xml中的namespace改为UserDao的路径

配置文件中的namespace中的名称为对应Mapper接口或Dao接口的完整包名,必须一致!

select

  ·select标签是mybatis中最常用的标签之一

  ·select语句有很多属性可以详细配置每一条SQL语句

     - SQL语句返回值类型。【完整的类名或者别名】

     - 传入SQL语句的参数类型 。

     - 命名空间中唯一的标识符

     - 接口中的方法名与映射文件中的SQL语句ID 一一对应

     - id

     - parameterType

     - resultType

需求:根据id查询用户

1.在UserMapper中添加对应方法

  public interface UserMapper {
  //查询全部用户
  List<User> selectUser();
  //根据id查询用户
  User selectUserById(int id);
}

2.在UserMapper.xml中添加Select语句

  <select id="selectUserById" resultType="com.kuang.pojo.User">
  select * from user where id = #{id}
  </select>

3.测试类中测试

  @Test
  public void tsetSelectUserById() {
     SqlSession session = MybatisUtils.getSession();  //获取SqlSession连接
     UserMapper mapper = session.getMapper(UserMapper.class);
     User user = mapper.selectUserById(1);
     System.out.println(user);
     session.close();
  }

如果参数过多,可以考虑使用Map实现,如果参数比较少,直接传递参数即可

insert

一般使用insert标签进项插入操作。

需求:给数据库增加一个用户

1.在UserMapper接口中添加对应的方法

  //添加一个用户
  int addUser(User user);

2.在UserMapper.xml中添加insert语句

  <insert id="addUser" parameterType="com.kuang.pojo.User">
      insert into user (id,name,pwd) values (#{id},#{name},#{pwd})
  </insert>

3.测试

  @Test
  public void testAddUser() {
     SqlSession session = MybatisUtils.getSession();
     UserMapper mapper = session.getMapper(UserMapper.class);
     User user = new User(5,"王五","zxcvbn");
     int i = mapper.addUser(user);
     System.out.println(i);
     session.commit(); //提交事务,重点!不写的话不会提交到数据库
     session.close();
  }
  **注意:增 删 改操作须要提交事务**

update

需求:修改用户信息

1.编写接口方法

  //修改一个用户
  int updateUser(User user);

2.编写对应的配置文件SQL

  <update id="updateUser" parameterType="com.kuang.pojo.User">
    update user set name=#{name},pwd=#{pwd} where id = #{id}
  </update>

3.测试

  @Test
  public void testUpdateUser() {
     SqlSession session = MybatisUtils.getSession();
     UserMapper mapper = session.getMapper(UserMapper.class);
     User user = mapper.selectUserById(1);
     user.setPwd("asdfgh");
     int i = mapper.updateUser(user);
     System.out.println(i);
     session.commit(); //提交事务,重点!不写的话不会提交到数据库
     session.close();
  }

delete

需求:根据id删除一个用户

1.编写接口方法

  //根据id删除用户
  int deleteUser(int id);

2.编写对应的配置文件SQL

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

3.测试

  @Test
  public void testDeleteUser() {
     SqlSession session = MybatisUtils.getSession();
     UserMapper mapper = session.getMapper(UserMapper.class);
     int i = mapper.deleteUser(5);
     System.out.println(i);
     session.commit(); //提交事务
     session.close();
  }

总结

  1.所有的增删改操作都需要提交事务!

  2.接口所有的普通参数,尽量都写上@Param参数,尤其是多个参数时,必须写上!

  3.有时候根据业务的需求,可以考虑使用map传递参数!

  4.为了规范操作,在SQL的配置文件中,我们尽量将Parameter参数和resultType都写上!

???????????思考:模糊查询Like语句怎么写????????????????????

第一种:在java代码中添加sql通配符

  string wildcardname = “%smi%”;
  list<name> names = mapper.selectlike(wildcardname);
  <select id=”selectlike”>
  select * from foo where bar like #{value}
  </select>

第二种:在sql语句中拼接通配符,会引起sql注入

  string wildcardname = “smi”;
  list<name> names = mapper.selectlike(wildcardname);
  <select id=”selectlike”>
      select * from foo where bar like "%"#{value}"%"
  </select>

核心配置文件

  mybatis-config.xml 系统核心配置文件

  MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。
  
  能配置的内容如下:

        configuration(配置)
        properties(属性)
        settings(设置)
        typeAliases(类型别名)
        typeHandlers(类型处理器)
        objectFactory(对象工厂)
        plugins(插件)
        environments(环境配置)
        environment(环境变量)
        transactionManager(事务管理器)
        dataSource(数据源)
        databaseIdProvider(数据库厂商标识)
        mappers(映射器)
        <!-- 注意元素节点的顺序!顺序不对会报错 -->

environment元素

     <environments default="development">
      <environment id="development">
        <transactionManager type="JDBC">
          <property name="..." value="..."/>
        </transactionManager>
        <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>


    配置Mybatis的多套运行环境,将SQL映射到多个不同的数据库上,必须指定其中一个为默认运行环境(通过default指定)

    子元素节点:**environment**

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

        `数据源是必须配置的。

        `有三种内建的数据源类型

          type="[UNPOOLED|POOLED|JNDI]")

        `unpooled:这个数据源的实现只是每次被请求时打开和关闭连接。

        `**pooled**:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来 , 这是一种使得并发 Web 应用快速响应请求的流行处理方式。

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

        `数据源也有很多第三方的实现,比如dbcp,c3p0,druid等等....

        `这两种事务管理器类型都不需要设置任何属性(id必须保证唯一)

        ` 子元素节点:transactionManager(事务管理器)
        
              <!-- 语法 -->
              <transactionManager type="[ JDBC | MANAGED ]"/>

        `子元素节点:**数据源(dataSource)**

mappers元素

mappers

  映射器 : 定义映射SQL语句文件

  既然 MyBatis 的行为其他元素已经配置完了,我们现在就要定义 SQL 映射语句了。但是首先
  
  我们需要告诉 MyBatis 到哪里去找到这些语句。Java 在自动查找这方面没有提供一个很好的方法,

  所以最佳的方式是告诉 MyBatis 到哪里去找映射文件。你可以使用相对于类路径的资源引用, 或完

  全限定资源定位符(包括 `file:///` 的 URL),或类名和包名等。映射器是MyBatis中最核心的组件

  之一,在MyBatis 3之前,只支持xml映射器,即:所有的SQL语句都必须在xml文件中配置。而从MyBatis3

  开始,还支持接口映射器,这种映射器方式允许以Java代码的方式注解定义SQL语句,非常简洁。

引入资源方式

  <!-- 使用相对于类路径的资源引用 -->
  <mappers>
   <mapper resource="org/mybatis/builder/PostMapper.xml"/>
  </mappers>
  <!-- 使用完全限定资源定位符(URL) -->
  <mappers>
   <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  </mappers>
  <!--
  使用映射器接口实现类的完全限定类名
  需要配置文件名称和接口名称一致,并且位于同一目录下
  -->
  <mappers>
   <mapper class="org.mybatis.builder.AuthorMapper"/>
  </mappers>
  <!--
  将包内的映射器接口实现全部注册为映射器
  但是需要配置文件名称和接口名称一致,并且位于同一目录下
  -->
  <mappers>
   <package name="org.mybatis.builder"/>
  </mappers>

Mapper文件

  <?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.mimiluyo.mapper.UserMapper">   
  </mapper>

namespace作用:

  1.namespace的命名(包名+类名)必须跟某个接口名同名

  2.接口中的方法与映射文件中sql语句id应该一一对应

  3.namespace和子元素的id联和保证唯一,区别不同的mapper

  4.绑定DAO接口

  Mybatis真正强大在于它的映射语句,映射器的XML文件就显得相对简单。

Properties 优化

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

第一步:在资源目录下新建一个db.properties

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

第二步:将文件导入properties配置文件

  <configuration>
     <!--导入properties文件-->
     <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>
     </environments>
     <mappers>
         <mapper resource="mapper/UserMapper.xml"/>
     </mappers>
  </configuration>

typeAliases优化

类型别名是为java类型设置一个短的名字。只和XML配置有关,存在的意义仅仅在于减少类完全限定名的冗余。

  <!--配置别名,注意顺序-->
  <typeAliases>
     <typeAlias type="com.mimiluyo.pojo.User" alias="User"/>
  </typeAliases>

也可以指定一个包名,Mybatis会在包名下搜索须要的javaBean

  <typeAliases>
     <package name="com.mimiluyo.pojo"/>
  </typeAliases>

每一个在包 com.mimiluyo.pojo 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。

若有注解,则别名为其注解值。

  @Alias("user")
  public class User {
    ...
  }

其他配置浏览

设置

  ·设置(settings)相关(查看帮助文档)

  ·懒加载,日志实现,缓存开启关闭

一个完整配置如下:

  <settings>
   <setting name="cacheEnabled" value="true"/>
   <setting name="lazyLoadingEnabled" value="true"/>
   <setting name="multipleResultSetsEnabled" value="true"/>
   <setting name="useColumnLabel" value="true"/>
   <setting name="useGeneratedKeys" value="false"/>
   <setting name="autoMappingBehavior" value="PARTIAL"/>
   <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
   <setting name="defaultExecutorType" value="SIMPLE"/>
   <setting name="defaultStatementTimeout" value="25"/>
   <setting name="defaultFetchSize" value="100"/>
   <setting name="safeRowBoundsEnabled" value="false"/>
   <setting name="mapUnderscoreToCamelCase" value="false"/>
   <setting name="localCacheScope" value="SESSION"/>
   <setting name="jdbcTypeForNull" value="OTHER"/>
   <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
  </settings>

类型处理器

  1.无论是Mybatis在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适方式转换成java类型

  2.可以重写类型处理器或创建你自己的烈性处理器来处理不支持的或非标准的类型

对象工厂

  ·MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。

  ·默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过有参构造方法来实例化。

  ·如果想覆盖对象工厂的默认行为,则可以通过创建自己的对象工厂来实现

生命周期和作用域

先分析下Mybatis的执行过程

作用域理解

  1.SqlSessionFactoryBuilder 的作用在于创建 SqlSessionFactory,创建成功后,

  SqlSessionFactoryBuilder 就失去了作用,所以它只能存在于创建 SqlSessionFactory 的方法中,

  而不要让其长期存在。因此 **SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域**(局部方法变量)

  2.SqlSessionFactory 可以被认为是一个数据库连接池,它的作用是创建 SqlSession 接口对象。因

  为 MyBatis 的本质就是 Java 对数据库的操作,所以 SqlSessionFactory 的生命周期存在于整个 

  MyBatis 的应用之中,所以一旦创建了 SqlSessionFactory,就要长期保存它,直至不再使用 
  
  MyBatis 应用,所以可以认为 SqlSessionFactory 的生命周期就等同于 MyBatis 的应用周期.

  3.由于 SqlSessionFactory 是一个对数据库的连接池,所以它占据着数据库的连接资源。如果创建

  多个 SqlSessionFactory,那么就存在多个数据库连接池,这样不利于对数据库资源的控制,也会导致
  
  数据库连接资源被消耗光,出现系统宕机等情况,所以尽量避免发生这样的情况。

  4.因此在一般的应用中我们往往希望 SqlSessionFactory 作为一个单例,让它在应用中被共享。

  所以说 **SqlSessionFactory 的最佳作用域是应用作用域。**

  5.如果说 SqlSessionFactory 相当于数据库连接池,那么 SqlSession 就相当于一个数据库连接

  (Connection 对象),你可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback 等

  方法,提交或者回滚事务。所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,
  
  让它归还给 SqlSessionFactory,否则数据库资源就很快被耗费精光,系统就会瘫痪,所以用

  try...catch...finally... 语句来保证其正确关闭。

  6.**所以 SqlSession 的最佳的作用域是请求或方法作用域。**

三,ResultMap及分页

要解决的问题:属性名和字段名不一致

1.查看之前的数据库的字段名

2.java中的实体类设计

  public class User {

     private int id;  //id
     private String name;   //姓名
     private String password;   //密码和数据库不一样!

     //构造
     //set/get
     //toString()
  }

3.接口

  //根据id查询用户
  User selectUserById(int id);

4.mapper映射文件

  <select id="selectUserById" resultType="user">
    select * from user where id = #{id}
  </select>

5.测试

  @Test
  public void testSelectUserById() {
     SqlSession session = MybatisUtils.getSession();  //获取SqlSession连接
     UserMapper mapper = session.getMapper(UserMapper.class);
     User user = mapper.selectUserById(1);
     System.out.println(user);
     session.close();
  }

结果:User{id=1,name=麋咪麓哟,password='null'}

查询出来的password为空,说明有问题!!!

分析:

  ·select * from user where id = #{id} 可以看做select  id,name,pwd  from user where id = #{id}

  ·mybatis会根据这些查询的列名(会将列名转化为小写,数据库不区分大小写) , 去对应的实体类中查找相应列名的set方法设值 , 由于找不到setPwd() , 所以password返回null ; [自动映射]

解决方法:

方法一:为列名指定别名,和java实体类的属性名一致。

  <select id="selectUserById" resultType="User">
    select id , name , pwd as password from user where id = #{id}
  </select>

方法二:使用结果集映射->RestultMap(推荐)

  <resultMap id="UserMap" type="User">
     <!-- id为主键 -->
     <id column="id" property="id"/>
     <!-- column是数据库表的列名 , property是对应实体类的属性名 -->
     <result column="name" property="name"/>
     <result column="pwd" property="password"/>
  </resultMap>

  <select id="selectUserById" resultMap="UserMap">
    select id , name , pwd from user where id = #{id}
  </select>

ResultMap

自动映射

  ·resultMap元素是Mybatis中最重要最强大的元素。它可以使开发者从绝大部分的JDBC ResultSets数据提取代码中解放出来。

  ·在为一些比如连接的复杂语句编写映射代码时,一份resultMap能够代替实现同等功能的长达千行的代码

  ·ResultMap的设计思想是,对于简单的语句根本不需要配置显式的结果映射,而对于复杂一点的语句只需要描述它们的关系即可

  <select id="selectUserById" resultType="map">
  select id , name , pwd
    from user
    where id = #{id}
  </select>

  这是简单映射语句,并没有显式指定resultMap,只是简单的将所有的列映射到HashMap的键上,这由resultType属性

  指定。对染在大部分情况下都够用,但HashMap不是一个很好的模型。(我们的程序可能会使用javaBean或POJO作为模型)

手动映射

1.返回值类型resultMap

  <select id="selectUserById" resultMap="UserMap">
    select id , name , pwd from user where id = #{id}
  </select>

2.编写resultMap,实现手动映射

  <resultMap id="UserMap" type="User">
     <!-- id为主键 -->
     <id column="id" property="id"/>
     <!-- column是数据库表的列名 , property是对应实体类的属性名 -->
     <result column="name" property="name"/>
     <result column="pwd" property="password"/>
  </resultMap>

分页的几种方式

日志工厂

  如果一个数据库相关操作出现问题,我们可以根据输出的SQL语句快速排查问题,对于以往的开发过程,我们会经

  常使用dabug模式调节,跟踪我们的代码执行过程,但是现在使用Mybatis是基于**接口,配置文件**的源代码执

  行过程。所以我们需要选择日志工具

Mybatis内置的日志工厂提供日志功能,有以下几种:

  ·SLF4J

  ·Apache Commons Logging

  ·Log4j 2

  ·Log4j

  ·JDKlogging

具体使用哪个日志实现工具有Mybatis的内置日志工厂确定。它会使用最先找到的(按上面列举的顺序查找)。如果一个都没找到,日志功能就会被禁用!

标准日志实现

  指定Mybatis应该使用哪个日志记录实现,如果此设置不存在则会泽东发现日志记录实现。

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

Log4j

简介

  ·Log4j是Apache的一个开源项目

  ·通过使用Log4j,我们可以控制日志信息输送的目的地:控制台,文本,GUI组件....

  ·我们也可以控制每一条日志的输出格式;

  ·通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以

    通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

使用步骤

1.导入log4j包

  <dependency>
     <groupId>log4j</groupId>
     <artifactId>log4j</artifactId>
     <version>1.2.17</version>
  </dependency>

2.配置文件编写

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

3.setting设置日志实现

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

4.在程序中使用Log4j输出

  //注意导包:org.apache.log4j.Logger
  static Logger logger = Logger.getLogger(MyTest.class);

  @Test
  public void selectUser() {
     logger.info("info:进入selectUser方法");
     logger.debug("debug:进入selectUser方法");
     logger.error("error: 进入selectUser方法");
     SqlSession session = MybatisUtils.getSession();
     UserMapper mapper = session.getMapper(UserMapper.class);
     List<User> users = mapper.selectUser();
     for (User user: users){
         System.out.println(user);
    }
     session.close();
  }

limit实现分页

为什么须要分页??

  在学习mybatis等持久层框架的时候,会经常对数据进行增删改查操作,使用最多的是对数据库进行查询操作,

  如果查询大量数据的时候,我们往往使用分页进行查询,也就是每次处理小部分数据,这样对数据库压力就在可控范围内。

使用Limit实现分页

  #语法
  SELECT * FROM table LIMIT stratIndex,pageSize

  SELECT * FROM table LIMIT 5,10; // 检索记录行 6-15  

  #为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1:   
  SELECT * FROM table LIMIT 95,-1; // 检索记录行 96-last.  

  #如果只给定一个参数,它表示返回最大的记录行数目:   
  SELECT * FROM table LIMIT 5; //检索前 5 个记录行  

  #换句话说,LIMIT n 等价于 LIMIT 0,n。

步骤:

1.修改Mapper文件

  <select id="selectUser" parameterType="map" resultType="user">
    select * from user limit #{startIndex},#{pageSize}
  </select>

2.Mapper接口,参数为map

  //选择全部用户实现分页
  List<User> selectUser(Map<String,Integer> map);

3.在测试类中传入参数测试

  **起始位置 = (当前页面 - 1) * 页面大小**

  //分页查询 , 两个参数startIndex , pageSize
  @Test
  public void testSelectUser() {
     SqlSession session = MybatisUtils.getSession();
     UserMapper mapper = session.getMapper(UserMapper.class);

     int currentPage = 1;  //第几页
     int pageSize = 2;  //每页显示几个
     Map<String,Integer> map = new HashMap<String,Integer>();
     map.put("startIndex",(currentPage-1)*pageSize);
     map.put("pageSize",pageSize);

     List<User> users = mapper.selectUser(map);

     for (User user: users){
         System.out.println(user);
    }

     session.close();
  }

RoeBounds分页

1.mapper接口

  //选择全部用户RowBounds实现分页
  List<User> getUserByRowBounds();

2.mapper文件

  <select id="getUserByRowBounds" resultType="user">
  select * from user
  </select>

3.测试类(使用Rowbounds类)

  @Test
  public void testUserByRowBounds() {
     SqlSession session = MybatisUtils.getSession();

     int currentPage = 2;  //第几页
     int pageSize = 2;  //每页显示几个
     RowBounds rowBounds = new RowBounds((currentPage-1)*pageSize,pageSize);

     //通过session.**方法进行传递rowBounds,[此种方式现在已经不推荐使用了]
     List<User> users =session.selectList("com.kuang.mapper.UserMapper.getUserByRowBounds", null,rowBounds);

     for (User user: users){
         System.out.println(user);
    }
     session.close();
  }

四,使用注解开发

面向接口编程

  ·**根本原因 :  解耦 , 可拓展 , 提高复用 , 分层开发中 , 上层不用管具体的实现 , 大家都遵守共同的标准 , 

  使得开发变得容易 , 规范性更好**

关于接口的理解

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

  ·接口的本身反映了系统设计人员对系统的抽象理解。

  ·接口有两类:

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

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

三个面向区别

  ·面向对象是指:我们考虑问题时以对象为单位,考虑它的属性和方法

  ·面向过程是指我们考虑问题时以一个具体的流程(事务过程)为单位,考虑它的实现

  ·解耦设计与非接口设计是针对复用技术而言的,与面向对象不是一个问题,更多的体现就是对系统整体的架构

注解开发

  ·**Mybatis最初配置信息是基于XML,映射语句(SQL)也是定义在XML中的,二到Mybatis 3提供了新的基于注解的配置。**

  ·**java注解的表达力和灵活性十分有限。最强大的Mybatis映射并不是用注解来构建

  ·sql类型分成:

        @select()

        @update()

        @Insert()

        @delete()

注意:使用注解开发就不需要mapper.xml映射文件了

1.在接口中添加注解

  //查询全部用户
  @Select("select id,name,pwd password from user")
  public List<User> getAllUser();

2.在Mybatis的核心配置文件中注入

  <!--使用class绑定接口-->
  <mappers>
     <mapper class="com.kuang.mapper.UserMapper"/>
  </mappers>

3.测试

  @Test
  public void testGetAllUser() {
     SqlSession session = MybatisUtils.getSession();
     //本质上利用了jvm的动态代理机制
     UserMapper mapper = session.getMapper(UserMapper.class);

     List<User> users = mapper.getAllUser();
     for (User user : users){
         System.out.println(user);
    }

     session.close();
  }

4.本质利用了jvm的动态代理机制

5.Mybatis详细的执行流程

注解增删改

修改MybatisUtils工具类的getSession()方法,实现重载(确保实体类和数据库字段对应)

   //获取SqlSession连接
    public static SqlSession getSession(){
        return getSession(true); //事务自动提交
    }

    public static SqlSession getSession(boolean flag){
        return sqlSessionFactory.openSession(flag);
    }

查询

1.编写接口方法注解

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

2.测试

  @Test
  public void testSelectUserById() {
     SqlSession session = MybatisUtils.getSession();
     UserMapper mapper = session.getMapper(UserMapper.class);

     User user = mapper.selectUserById(1);
     System.out.println(user);

     session.close();
  }

新增

1.编写接口方法注解

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

2.测试

  @Test
  public void testAddUser() {
     SqlSession session = MybatisUtils.getSession();
     UserMapper mapper = session.getMapper(UserMapper.class);

     User user = new User(6, "麋咪麓哟", "123456");
     mapper.addUser(user);

     session.close();
  }

修改

1.编写接口方法注解

  //修改一个用户
  @Update("update user set name=#{name},pwd=#{pwd} where id = #{id}")
  int updateUser(User user);

2.测试

  @Test
  public void testUpdateUser() {
     SqlSession session = MybatisUtils.getSession();
     UserMapper mapper = session.getMapper(UserMapper.class);

     User user = new User(6, "秦疆", "zxcvbn");
     mapper.updateUser(user);

     session.close();
  }

3.删除

1.编写接口方法注解

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

2.测试

  @Test
  public void testDeleteUser() {
     SqlSession session = MybatisUtils.getSession();
     UserMapper mapper = session.getMapper(UserMapper.class);

     mapper.deleteUser(6);

     session.close();
  }

增删改一定要对事物处理

关于@Param

@Param注解用于给方法参数一个名字

  ·在方法只接受一个参数的情况下,可以不使用@Param。

  ·在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。

  ·如果参数是 JavaBean , 则不能使用@Param。

  ·不使用@Param注解时,参数只能有一个,并且是Javabean。

与 $ 的区别

  ·#{}的作用主要是替换预编译语句(PrepareStatement)中的占位符?

  INSERT INTO user (name) VALUES (#{name});
  INSERT INTO user (name) VALUES (?);

  ·${}的作用是直接进行字符串替换

  INSERT INTO user (name) VALUES ('${name}');
  INSERT INTO user (name) VALUES ('mimiluyo');

  使用注解和配置文件协同开发,才是Mybatis的最佳实践!!

五,一对多和多对一处理

多对一处理

数据库设计

  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');

搭建测试环境

1.IDEA安装Lombok插件

2.引入Maven依赖

  <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
  <dependency>
   <groupId>org.projectlombok</groupId>
   <artifactId>lombok</artifactId>
   <version>1.16.10</version>
  </dependency>

3.在代码中增加注解

  @Data //GET,SET,ToString,有参,无参构造
  public class Teacher {
     private int id;
     private String name;
  }
  @Data
  public class Student {
     private int id;
     private String name;
     //多个学生可以是同一个老师,即多对一
     private Teacher teacher;
  }

4.编写实体类对应的Mapper接口(无论有没有需要,都应该写上)

  public interface StudentMapper {
  }
  public interface TeacherMapper {
  }

5.编写Mapper接口对应的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.kuang.mapper.StudentMapper">

  </mapper>
  <?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.mimiluyo.mapper.TeacherMapper">

  </mapper>

按查询嵌套处理

1.给StudentMapper接口增加方法

  //获取所有学生及对应老师的信息
  public List<Student> getStudents();

2.编写对应的Mapper文件

  <?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.kuang.mapper.StudentMapper">

     <!--
     需求:获取所有学生及对应老师的信息
     思路:
         1. 获取所有学生的信息
         2. 根据获取的学生信息的老师ID->获取该老师的信息
         3. 思考问题,这样学生的结果集中应该包含老师,该如何处理呢,数据库中我们一般使用关联查询?
             1. 做一个结果集映射:StudentTeacher
             2. StudentTeacher结果集的类型为 Student
             3. 学生中老师的属性为teacher,对应数据库中为tid。
                多个 [1,...)学生关联一个老师=> 一对一,一对多
             4. 查看官网找到:association – 一个复杂类型的关联;使用它来处理关联查询
     -->
     <select id="getStudents" resultMap="StudentTeacher">
      select * from student
     </select>
     <resultMap id="StudentTeacher" type="Student">
         <!--association关联属性 property属性名 javaType属性类型 column在多的一方的表中的列名-->
         <association property="teacher"  column="tid" javaType="Teacher"select="getTeacher"/>
     </resultMap>
     <!--
     这里传递过来的id,只有一个属性的时候,下面可以写任何值
     association中column多参数配置:
         column="{key=value,key=value}"
         其实就是键值对的形式,key是传给下个sql的取值名称,value是片段一中sql查询的字段名。
     -->
     <select id="getTeacher" resultType="teacher">
        select * from teacher where id = #{id}
     </select>

  </mapper>

3.编写完毕去Mybatis配置文件中,注册Mapper!

4.注意点:

  <resultMap id="StudentTeacher" type="Student">
     <!--association关联属性 property属性名 javaType属性类型 column在多的一方的表中的列名-->
     <association property="teacher"  column="{id=tid,name=tid}"javaType="Teacher"
  </resultMap>
  <!--
  这里传递过来的id,只有一个属性的时候,下面可以写任何值
  association中column多参数配置:
     column="{key=value,key=value}"
     其实就是键值对的形式,key是传给下个sql的取值名称,value是片段一中sql查询的字段名。
  -->
  <select id="getTeacher" resultType="teacher">
    select * from teacher where id = #{id} and name = #{name}
  </select>

5.测试

  @Test
  public void testGetStudents(){
     SqlSession session = MybatisUtils.getSession();
     StudentMapper mapper = session.getMapper(StudentMapper.class);

     List<Student> students = mapper.getStudents();

     for (Student student : students){
         System.out.println(
                 "学生名:"+ student.getName()+"	老师:"+student.getTeacher().getName());
    }
  }

按结果嵌套处理

1.接口方法编写

  public List<Student> getStudents2();

2.编写对应的mapper文件

  <!--
  按查询结果嵌套处理
  思路:
     1. 直接查询出结果,进行结果集的映射
  -->
  <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">
     <id property="id" column="sid"/>
     <result property="name" column="sname"/>
     <!--关联对象property 关联对象在Student实体类中的属性-->
     <association property="teacher" javaType="Teacher">
         <result property="name" column="tname"/>
     </association>
  </resultMap>

3.在mybatis-config文件中注入(前面已经写过)

4.测试

  @Test
  public void testGetStudents2(){
     SqlSession session = MybatisUtils.getSession();
     StudentMapper mapper = session.getMapper(StudentMapper.class);

     List<Student> students = mapper.getStudents2();

     for (Student student : students){
         System.out.println("学生名:"+ student.getName()+"	老师:"+student.getTeacher().getName());
    }
  }

总结:

  按查询嵌套处理就像SQL中的子查询

  按结果进行嵌套处理就像SQL中的联表查询

一对多处理

实体类编写

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

按结果嵌套处理

1.TeacherMapper接口编写方法

  //获取指定老师,及老师下的所有学生
  public Teacher getTeacher(int id);

2.编写接口对应的Mapper配置文件

  <mapper namespace="com.kuang.mapper.TeacherMapper">
     <!--
     思路:
         1. 从学生表和老师表中查出学生id,学生姓名,老师姓名
         2. 对查询出来的操作做结果集映射
             1. 集合的话,使用collection!
                 JavaType和ofType都是用来指定对象类型的
                 JavaType是用来指定pojo中属性的类型
                 ofType指定的是映射到list集合属性中pojo的类型。
     -->
     <select id="getTeacher" resultMap="TeacherStudent">
        select s.id sid, s.name sname , t.name tname, t.id tid
        from student s,teacher t
        where s.tid = t.id and t.id=#{id}
     </select>

     <resultMap id="TeacherStudent" type="Teacher">
         <result  property="name" column="tname"/>
         <collection property="students" ofType="Student">
             <result property="id" column="sid" />
             <result property="name" column="sname" />
             <result property="tid" column="tid" />
         </collection>
     </resultMap>
  </mapper>

3.将Mapper文件注册到MyBatis-config文件中

  <mappers>
     <mapper resource="mapper/TeacherMapper.xml"/>
  </mappers>

4.测试

  @Test
  public void testGetTeacher(){
     SqlSession session = MybatisUtils.getSession();
     TeacherMapper mapper = session.getMapper(TeacherMapper.class);
     Teacher teacher = mapper.getTeacher(1);
     System.out.println(teacher.getName());
     System.out.println(teacher.getStudents());
  }

按查询嵌套处理

1.TeacherMapper接口编写方法

  public Teacher getTeacher2(int id);

2.编写接口对应的Mapper配置文件

  <select id="getTeacher2" resultMap="TeacherStudent2">
  select * from teacher where id = #{id}
  </select>
  <resultMap id="TeacherStudent2" type="Teacher">
     <!--column是一对多的外键 , 写的是一的主键的列名-->
     <collection property="students" javaType="ArrayList" ofType="Student"column="id" select="getStudentByTeacherId"/>
  </resultMap>
  <select id="getStudentByTeacherId" resultType="Student">
    select * from student where tid = #{id}
  </select>

3.将Mapper文件注册到Mybatis-conffig文件中

4.测试

  @Test
  public void testGetTeacher2(){
     SqlSession session = MybatisUtils.getSession();
     TeacherMapper mapper = session.getMapper(TeacherMapper.class);
     Teacher teacher = mapper.getTeacher2(1);
     System.out.println(teacher.getName());
     System.out.println(teacher.getStudents());
  }

小结:

  1、关联-association

  2、集合-collection

  3、所以association是用于一对一和多对一,而collection是用于一对多的关系

  4、JavaType和ofType都是用来指定对象类型的

        JavaType是用来指定pojo中属性的类型

        ofType指定的是映射到list集合属性中pojo的类型。

注意说明:

  1、保证SQL的可读性,尽量通俗易懂

  2、根据实际要求,尽量编写性能更高的SQL语句

  3、注意属性名和字段不一致的问题

  4、注意一对多和多对一 中:字段和属性对应的问题

  5、尽量使用Log4j,通过日志来查看自己的错误

六,动态SQL

什么是动态SQL:动态SQL指的是根据不同的查询条件,生成不同的Sql语句

搭建环境

新建一个数据库表:blog

字段:id,title,author,create_time,views

  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

1.创建Mybatis基础工程

2.IDutil工具类

  public class IDUtil {
     public static String genId(){
         return UUID.randomUUID().toString().replaceAll("-","");
    }
  }

3.实体类编写(set(),get()方法使用lombok注解)

  import java.util.Date;
  @Date
  public class Blog {
     private String id;
     private String title;
     private String author;
     private Date createTime;
     private int views;
     //set,get....
  }

4.编写Mapper接口和xml文件

  public interface BlogMapper {
  
  }
  <?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.mimiluyo.mapper.BlogMapper">

  </mapper>

5.mybatis核心配置文件,下划线驼峰自动转换

  <settings>
     <setting name="mapUnderscoreToCamelCase" value="true"/>
     <setting name="logImpl" value="STDOUT_LOGGING"/>
  </settings>
  <!--注册Mapper.xml-->
  <mappers>
   <mapper resource="mapper/BlogMapper.xml"/>
  </mappers>

6.插入初始数据

编写接口

  //新增一个博客
  int addBlog(Blog blog);

sql配置文件

  <insert id="addBlog" parameterType="blog">
    insert into blog (id, title, author, create_time, views)
    values (#{id},#{title},#{author},#{createTime},#{views});
  </insert>

初始化博客方法

  @Test
  public void addInitBlog(){
     SqlSession session = MybatisUtils.getSession();
     BlogMapper mapper = session.getMapper(BlogMapper.class);

     Blog blog = new Blog();
     blog.setId(IDUtil.genId());
     blog.setTitle("Mybatis");
     blog.setAuthor("梦✨雲");
     blog.setCreateTime(new Date());
     blog.setViews(9999);

     mapper.addBlog(blog);

     blog.setId(IDUtil.genId());
     blog.setTitle("Java");
     mapper.addBlog(blog);

     blog.setId(IDUtil.genId());
     blog.setTitle("Spring");
     mapper.addBlog(blog);

     blog.setId(IDUtil.genId());
     blog.setTitle("微服务");
     mapper.addBlog(blog);

     session.close();
  }

数据初始化完成!

if语句

需求:根据作者名字和博客名字来查询博客,如果作者名为空,则只根据博客名查询,反之根据作者名查询

1.编写接口类

  //需求1
  List<Blog> queryBlogIf(Map map);

2.编写SQL语句

  <!--需求1:
  根据作者名字和博客名字来查询博客!如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询
  select * from blog where title = #{title} and author = #{author}
  -->
  <select id="queryBlogIf" parameterType="map" resultType="blog">
    select * from blog where
     <if test="title != null">
        title = #{title}
     </if>
     <if test="author != null">
        and author = #{author}
     </if>
  </select>

3.测试

  @Test
  public void testQueryBlogIf(){
     SqlSession session = MybatisUtils.getSession();
     BlogMapper mapper = session.getMapper(BlogMapper.class);

     HashMap<String, String> map = new HashMap<String, String>();
     map.put("title","Mybatis");
     map.put("author","梦✨雲");
     List<Blog> blogs = mapper.queryBlogIf(map);

     System.out.println(blogs);
  
     session.close();
  }

如果author为null,查询语句为select * from user where title=#{title},如果title为空那么查询语句为 select * from user where and author=#{author},这是错误的 SQL 语句,如何解决呢?看下面的where语句

where

修改上面的SQL语句

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

“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉

Set

对于上面查询SQL语包含where,如果在进行更新操作时,含有ser关键字,如何处理呢?

1.编写接口方法

  int updateBlog(Map map);

2.sql配置文件

  <!--注意set是用的逗号隔开-->
  <update id="updateBlog" parameterType="map">
    update blog
       <set>
           <if test="title != null">
              title = #{title},
           </if>
           <if test="author != null">
              author = #{author}
           </if>
       </set>
    where id = #{id};
  </update>

3.测试

  @Test
  public void testUpdateBlog(){
     SqlSession session = MybatisUtils.getSession();
     BlogMapper mapper = session.getMapper(BlogMapper.class);

     HashMap<String, String> map = new HashMap<String, String>();
     map.put("title","动态SQL");
     map.put("author","梦✨雲");
     map.put("id","9d6a763f5e1347cebda43e2a32687a77");

     mapper.updateBlog(map);
     session.close();
  }

choose语句

有时候我们不想用到所有的查询条件,执行选择其中的一个,查询条件有一个满足即可,使用choose标签可以解决此问题(类似于java的switch)

1.编写接口方法

  List<Blog> queryBlogChoose(Map map);

2.sql配置文件

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

3.测试

  @Test
  public void testQueryBlogChoose(){
     SqlSession session = MybatisUtils.getSession();
     BlogMapper mapper = session.getMapper(BlogMapper.class);

     HashMap<String, Object> map = new HashMap<String, Object>();
     map.put("title","Java");
     map.put("author","梦✨雲");
     map.put("views",9999);
     List<Blog> blogs = mapper.queryBlogChoose(map);

     System.out.println(blogs);

     session.close();
  }

SQL片段

有时候可能某个sql语句我们用的特别多,为了增加代码的复用性,简化代码,我们须要将这些代码抽取出来,然后直接调用

提取SQL片段

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

引用SQL片段

  <select id="queryBlogIf" parameterType="map" resultType="blog">
    select * from blog
     <where>
         <!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace -->
         <include refid="if-title-author"></include>
         <!-- 在这里还可以引用其他的 sql 片段 -->
     </where>
  </select>

注意:

  1.最好·基于单表定义片段,提高代码的可重用性

  2.在sql片段中不要包括where

Foreach

将数据库中前三个数据的id修改为1,2,3

需求:我们需要查询blog表中id分别为1,2,3的博客信息

1.编写接口

  List<Blog> queryBlogForeach(Map map);

2.编写SQL语句

  <select id="queryBlogForeach" parameterType="map" resultType="blog">
    select * from blog
     <where>
         <!--
         collection:指定输入对象中的集合属性
         item:每次遍历生成的对象
         open:开始遍历时的拼接字符串
         close:结束时拼接的字符串
         separator:遍历对象之间需要拼接的字符串
         select * from blog where 1=1 and (id=1 or id=2 or id=3)
       -->
         <foreach collection="ids"  item="id" open="and (" close=")" separator="or">
            id=#{id}
         </foreach>
     </where>
  </select>

3.测试

  @Test
  public void testQueryBlogForeach(){
     SqlSession session = MybatisUtils.getSession();
     BlogMapper mapper = session.getMapper(BlogMapper.class);

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

     List<Blog> blogs = mapper.queryBlogForeach(map);

     System.out.println(blogs);
     session.close();
  }

七,缓存(cache)

简介

  1.什么是缓存?

        ·存在内存中的临时数据

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

  2.为什么使用缓存?

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

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

        ·经常查询并且不经常修改的数据

Mybatis缓存

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

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

        默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)

        二级缓存需要手动开启和配置,他是基于namespace级别的缓存。

        为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

一级缓存

一级缓存也叫本地缓存

  ·与数据库同一次会话期间查询到的数据会放在本地缓存中。

  ·以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;

测试

1.在mybatis中加入日志,方便测试结果

2.编写接口方法

  //根据id查询用户
  User queryUserById(@Param("id") int id);

3.接口对应的Mapper文件

  <select id="queryUserById" resultType="user">
    select * from user where id = #{id}
  </select>

4.测试

  @Test
  public void testQueryUserById(){
     SqlSession session = MybatisUtils.getSession();
     UserMapper mapper = session.getMapper(UserMapper.class);

     User user = mapper.queryUserById(1);
     System.out.println(user);
     User user2 = mapper.queryUserById(1);
     System.out.println(user2);
     System.out.println(user==user2);

     session.close();
  }

一级缓存失效的四种情况

一级缓存是SqlSession级别的缓存,是一直开启的,我们无法关闭

一级缓存失效情况:没有用到当前的一级缓存,效果就是还要再向数据库中发起一次查询请求

1.sqlSession不同

  @Test
  public void testQueryUserById(){
     SqlSession session = MybatisUtils.getSession();
     SqlSession session2 = MybatisUtils.getSession();
     UserMapper mapper = session.getMapper(UserMapper.class);
     UserMapper mapper2 = session2.getMapper(UserMapper.class);

     User user = mapper.queryUserById(1);
     System.out.println(user);
     User user2 = mapper2.queryUserById(1);
     System.out.println(user2);
     System.out.println(user==user2);

     session.close();
     session2.close();
  }

每个sqlSession中的缓存相互独立

2.sqlSession相同,查询条件不同

  @Test
  public void testQueryUserById(){
     SqlSession session = MybatisUtils.getSession();
     UserMapper mapper = session.getMapper(UserMapper.class);
     UserMapper mapper2 = session.getMapper(UserMapper.class);

     User user = mapper.queryUserById(1);
     System.out.println(user);
     User user2 = mapper2.queryUserById(2);
     System.out.println(user2);
     System.out.println(user==user2);

     session.close();
  }

3.sqlSession相同,两次查询之间执行了增删改操作

增加方法

  //修改用户
  int updateUser(Map map);

编写SQL

  <update id="updateUser" parameterType="map">
    update user set name = #{name} where id = #{id}
  </update>

测试

  @Test
  public void testQueryUserById(){
     SqlSession session = MybatisUtils.getSession();
     UserMapper mapper = session.getMapper(UserMapper.class);

     User user = mapper.queryUserById(1);
     System.out.println(user);

     HashMap map = new HashMap();
     map.put("name","mimiluyo");
     map.put("id",4);
     mapper.updateUser(map);

     User user2 = mapper.queryUserById(1);
     System.out.println(user2);

     System.out.println(user==user2);

     session.close();
  }

4.sqlSession相同,手动清除一级缓存

  @Test
  public void testQueryUserById(){
     SqlSession session = MybatisUtils.getSession();
     UserMapper mapper = session.getMapper(UserMapper.class);
  
     User user = mapper.queryUserById(1);
     System.out.println(user);

     session.clearCache();//手动清除缓存

     User user2 = mapper.queryUserById(1);
     System.out.println(user2);

     System.out.println(user==user2);

     session.close();
  }
  一级缓存就是一个map

二级缓存

  ·二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存

  ·基于namespace级别的缓存,一个名称空间,对应一个二级缓存;

  ·工作机制

        一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;

        如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;

        新的会话查询信息,就可以从二级缓存中获取内容;

        不同的mapper查出的数据会放在自己对应的缓存(map)中;

使用步骤

1.开启全局缓存(mybatis-config.xml)

  <setting name="cacheEnabled" value="true"/>

2.去每个mapper.xml中配置使用二级缓存

  <cache/>
  官方示例=====>查看官方文档
  <cache
   eviction="FIFO"
   flushInterval="60000"
   size="512"
   readOnly="true"/>
  这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而

  且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

3.测试

  ·**所有的实体类先实现序列化接口**

  @Test
  public void testQueryUserById(){
     SqlSession session = MybatisUtils.getSession();
     SqlSession session2 = MybatisUtils.getSession();

     UserMapper mapper = session.getMapper(UserMapper.class);
     UserMapper mapper2 = session2.getMapper(UserMapper.class);

     User user = mapper.queryUserById(1);
     System.out.println(user);
     session.close();

     User user2 = mapper2.queryUserById(1);
     System.out.println(user2);
     System.out.println(user==user2);

     session2.close();
  }

结论:

  1.只要开启了二级缓存,我们在同一个Mapper中查询,就可以在二级缓存中拿到数据

  2.查出的数据都会被默认先放在一级缓存中

  3.只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中

缓存原理图

EhCache

EhCache是一种广泛使用的java分布式缓存,用于通用缓存,(使用前,先导入jar包)

  <!-- 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.xml中使用对应的缓存

  <mapper namespace = “org.acme.FooMapper” > 
     <cache type = “org.mybatis.caches.ehcache.EhcacheCache” /> 
  </mapper>

编写ehcache.xml文件,如果在加载时未找到/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>
原文地址:https://www.cnblogs.com/ysera-y/p/13681387.html