Mybatis学习笔记

MyBatis

1. MyBatis简介

1.1 什么是MyBatis?

  • MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。
  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
  • MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
  • MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。
  • 2013年11月迁移到Github。

1.2 如何获取MyBatis?

  • maven仓库:https://mvnrepository.com/artifact/org.mybatis/mybatis

    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.2</version>
    </dependency>
    
  • github :https://github.com/mybatis/mybatis-3

  • 中文官方文档:https://mybatis.org/mybatis-3/zh/index.html

1.3 ORM

​ 对象关系映射(Object Relational Mapping,简称ORM)是通过使用描述对象和数据库之间映射的元数据,将面向对象语言程序中的对象自动持久化到关系数据库中。本质上就是将数据从一种形式转换到另外一种形式。

2. MyBatis入门

2.1 数据准备

  • 在数据库中准备我们需要的测试数据,我们使用mysql
CREATE table `student` (
	`s_id` int PRIMARY key auto_increment ,
	`s_name` VARCHAR(10) ,
	`s_pwd` VARCHAR(10)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

insert into `student`(`s_name` ,`s_pwd`) VALUES ('zhangsan','111')
,('zhangsan','222')
,('zhaoliu','333')
,('tianqi','444')

select * from `student`

2.2 新建项目

  • ​ 创建maven项目,在pom中引入相关依赖。
		<!-- mysql 驱动包 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.46</version>
		</dependency>

		<!-- mybatis -->
		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis</artifactId>
			<version>3.5.2</version>
		</dependency>

	<!-- junit -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>

2.3 核心配置文件

  • 在resources目录下创建mybatis核心配置文件mybatis-config.xml(也可以叫其它名字)。主要用来跟数据库的连接。内容如下:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
      PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <!-- 根标签 -->
    <configuration>
    
    	<settings>
    	<!-- 配置数据库字段名跟属性名小驼峰匹配 -->
          <setting name="mapUnderscoreToCamelCase" value="true"/>
        </settings>
    
    	<!-- 环境:可以配置多个,default:指定采用哪个环境 -->
    	<environments default="development">
    		<environment id="development">
    			<!-- 事务管理器,JDBC类型的事务管理器 -->
    			<transactionManager type="JDBC" />
    			<!-- 数据源,池类型的数据源 -->
    			<dataSource type="POOLED">
    				<property name="driver" value="com.mysql.jdbc.Driver" />  
    				<property name="url"
    					value="jdbc:mysql://localhost:3306/mybatis-db?useUnicode=true&amp;characterEncoding=UTF-8" />
    				<property name="username" value="root" />
    				<property name="password" value="root" />
    			</dataSource>
    		</environment>
    	</environments>
    
       <!-- 指向映射的mapper文件路径 --> 
    	<mappers>
    		<mapper resource="com/seven/demo/dao/StudentMapper.xml" />
    	</mappers>
    
    </configuration>
    

2.4 准备JavaBean

  • 准备跟数据库对应的JavaBean类,由于上面配置开启了mapUnderscoreToCamelCase的设置,故此处的属性命名可以使用对应的小驼峰命名规则。

    package com.seven.demo.bean;
    
    public class User {
    	
    	private int sId  ;
    	
    	private String sName = "" ;
    	
    	private String sPwd = "" ;
    	
    	public int getsId() {
    		return sId;
    	}
    
    	public void setsId(int sId) {
    		this.sId = sId;
    	}
    
    	public String getsName() {
    		return sName;
    	}
    
    	public void setsName(String sName) {
    		this.sName = sName;
    	}
    
    	public String getsPwd() {
    		return sPwd;
    	}
    
    	public void setsPwd(String sPwd) {
    		this.sPwd = sPwd;
    	}
    
    	@Override
    	public String toString() {
    		return "User [sId=" + sId + ", sName=" + sName + ", sPwd=" + sPwd + "]";
    	}
    
    }
    
    

2.5 Mapper接口

  • 编写访问数据库的Mapper接口,以前的DAO在mybatis中叫Mapper。

    public interface StudentMapper {
    	
    	public List<User>  queryAllUser();
    	
    	public User queryUserById(int userId);
    	
    	public int insertUser(User user);
    	
    	public int updateUser(User user);
    	
    	public int deleteUser(int userId);
    
    }
    

2.6 Mapper配置

  • 在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">
      
      <!-- namespace:绑定一个dao/mapper中的接口,相当于mapper接口的实现 -->
    <mapper namespace="com.seven.demo.dao.StudentMapper">
    
      <!-- id:对应接口中的方法名    resultType:方法返回的集合中的javabean类型  -->
      <select id="queryAllUser" resultType="com.seven.demo.bean.User">
        select * from student 
      </select>
      
      <!--  #{}表示值,跟方法中的形参列表中的名称一致 -->
      <select id="queryUserById" parameterType="int" resultType="com.seven.demo.bean.User">
        select * from student where s_id = #{userId}
      </select>
      
    <insert id="insertUser" parameterType="com.seven.demo.bean.User">
      	insert into student (s_name ,s_pwd) values(#{sName},#{sPwd});
      </insert>
      
      <update id="updateUser" parameterType="com.seven.demo.bean.User">
      	update student set s_name=#{sName} ,s_pwd=#{sPwd} where s_id=#{sId}
      </update>
      
      <delete id="deleteUser" parameterType="int" >
      	delete from student where s_id = #{userId}
      </delete>
      
    </mapper>
    

tip:

如果是在idea开发中,不会把资源文件打包,执行会报mapper.xml文件找不到。故需在pom文件中进行如下配置

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

2.7 测试类

2.7.1 查询
	public static void main(String[] args) throws IOException {
		
//		获取核心配置文件对应的输入流
		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//		通过SqlSessionFactoryBuilder对象获取SqlSessionFactory对象
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//		通过SqlSessionFactory对象获取SqlSession对象
		try (SqlSession session = sqlSessionFactory.openSession();){

//			方式1(推荐):通过Mapper来使用
			StudentMapper studentMapper = session.getMapper(StudentMapper.class);
			List<User> ours = studentMapper.queryAllUser();
			System.out.println(ours);
            User user = studentMapper.queryUserById(2);
			System.out.println(user);
			
//			方式2:通过接口的全限定名来访问
			List<User>  list = session.selectList("com.seven.demo.dao.StudentMapper.queryAllUser");
			System.out.println(list);
            User user2 = session.selectOne("com.seven.demo.dao.StudentMapper.queryUserById",1);
			System.out.println(user2);
			
            
		}

	}
2.7.2 新增
  public static void main(String[] args) throws IOException {
  		
  //		获取核心配置文件对应的输入流
  		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
  //		通过SqlSessionFactoryBuilder对象获取SqlSessionFactory对象
  		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  //		通过SqlSessionFactory对象获取SqlSession对象
  		try (SqlSession session = sqlSessionFactory.openSession();){
  			StudentMapper studentMapper = session.getMapper(StudentMapper.class);
  			User u = new User();
  			u.setsName("seven");
  			u.setsPwd("333");
  			int i = studentMapper.insertUser(u);
  			if(i>0){
  				System.out.println("插入成功。");
  			}
  //			增/删/改操作必须要提交事务才能生效
  			session.commit();	
  			
  		}
  
  	}
  • tip: 如果通过openSession方法传入参数true的话sqlSessionFactory.openSession(true),则会自动提交事务。
2.7.3 修改
public static void main(String[] args) throws IOException {
		
//		获取核心配置文件对应的输入流
		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//		通过SqlSessionFactoryBuilder对象获取SqlSessionFactory对象
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//		通过SqlSessionFactory对象获取SqlSession对象
		try (SqlSession session = sqlSessionFactory.openSession();){
			StudentMapper studentMapper = session.getMapper(StudentMapper.class);
			User u = new User();
			u.setsId(2);
			u.setsName("seven");
			u.setsPwd("333");
			
			int i = studentMapper.updateUser(u);
			if(i>0){
				System.out.println("修改成功。");
			}
//			增/删/改操作必须要提交事务才能生效
			session.commit();
            
		}

	}
2.7.4 删除
public static void main(String[] args) throws IOException {
		
//		获取核心配置文件对应的输入流
		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//		通过SqlSessionFactoryBuilder对象获取SqlSessionFactory对象
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//		通过SqlSessionFactory对象获取SqlSession对象
		try (SqlSession session = sqlSessionFactory.openSession();){
			StudentMapper studentMapper = session.getMapper(StudentMapper.class);
			int i = studentMapper.deleteUser(3);
		 
			if(i>0){
				System.out.println("删除成功。");
			}
//			增/删/改操作必须要提交事务才能生效
			session.commit();		
		}

	}

2.8 生命周期跟作用域

生命周期、作用域是至关重要的,因为错误的使用会导致非常严重的并发问题。

  • SqlSessionFactoryBuilder:这个对象的作用是为了得到SqlSessionFactory对象,一旦得到,则此对象不需要了。因此此对象最好用在临时变量。

  • SqlSessionFactory:可以想象成生产SqlSession的工厂,一旦创建就应该在应用的运行期间一直存在,没有任何理由丢弃或者重新创建。因此此对象最佳作用域是应用区域。最简单的就是单例模式。

  • SqlSession:连接到连接池的一个请求。这个实例并不是线程安全的,因此不能被共享,所以它最佳的作用域是请求作用域或者方法作用域。用完之后记得关闭,不然会造成资源的浪费。

3. MyBatis核心配置

3.1 环境配置(environments)

  • MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中。例如,开发、测试和生产环境需要有不同的配置。**尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。**为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可。

    可以接受环境配置的两个方法签名是:

    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);
    

    如果忽略了环境参数,那么将会加载默认环境,如下所示:

    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader);
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, properties);
    
    
    environments 元素下可以定义多个environment元素,每个environment都可以id命名,而environments 通过default属性值来定默认操作environment的环境。
    
    
  • MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):

    设置成JDBC表示使用数据源的事务管理,设置成MANAGED使用容器的事务管理。如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置

  • dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。dataSource元素type="[UNPOOLED|POOLED|JNDI]"。分别表示不使用池/使用池/服务器的JNDI服务来获取数据的连接。

3.2 属性(properties)

  • 配置文件中dataSource中连接数据库的一些信息我们可以直接设置,也可以通过属性properties设置。

  • 新建外部db.properties文件:

    driver=com.mysql.jdbc.Driver
    url=jdbc:mysql://localhost:3306/mybatis-db?useUnicode=true&characterEncoding=UTF-8
    username=root
    password=root
    
    

然后在核心配置文件中引入:

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

然后在DataSource中引入这些字段即可。

<dataSource type="POOLED">
				<property name="driver" value="${driver}" />  
				<property name="url" value="${url}" />
				<property name="username" value="${username}" />
				<property name="password" value="${password}" />
</dataSource>

此外,我们还可以在创建SqlSessionFactory对象的时候设置属性:

Properties p = new Properties();
p.put("driver", "com.mysql.jdbc.Driver");
p.put("url", "jdbc:mysql://localhost:3306/mybatis-db?useUnicode=true&characterEncoding=UTF-8");
p.put("username", "root");
p.put("password", "root");
		
SqlSessionFactory sqlSessionFactory = new qlSessionFactoryBuilder().build(inputStream,p);

此时,这3处地方的属性全部会被读取,java中的设置方式优先级>外部properties文件>xml配置文件中的定义

3.3 设置(settings)

  <!-- 配置数据库字段名跟属性名小驼峰匹配 -->
   <setting name="mapUnderscoreToCamelCase" value="true"/>
  <!-- 设置日志的实现方式 -->
   <setting name="logImpl" value="STDOUT_LOGGING"/>
  <!--  二级全局开关是默认开启的,但为了有好的可读性最好也写上 -->
  <setting name="cacheEnabled" value="true"/> 
  <!--  获取参数映射的时候可以通过#{0} #{1}..来映射,不推荐 -->
  <setting name="useActualParamName" value="false" />
   <!--  打开全局关联查询懒加载的开关 -->
   <setting name="lazyLoadingEnabled" value="true"/>

3.4 类型别名(typeAliases)

  • 类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。主要通过配置文件中的typeAliases标签来定义。

  • 有2种方式:

    第1种是直接定义类型别名

 <typeAliases>
     <!-- 给com.seven.demo.bean.User类型取个别名user,在映射文件中可以直接使用别名 -->
    	<typeAlias type="com.seven.demo.bean.User" alias="user" />
 </typeAliases>

​ 第2种方式是直接扫描某个包,给所有bean类型自动取别名,规则为类名的首字符小写。

<typeAliases>
    	<!-- 把com.seven.demo.bean包下的所有bean取名别   -->
    	<package name="com.seven.demo.bean"/>
    </typeAliases>

如果这种默认取名规则我们不需要,可以通过注解来DIY:

//给此bean类型取个类型别名hello,在映射文件中可以直接使用此别名
@Alias("hello")
public class User {
	......
}

  • mybatis还为常见的 Java 类型内建的类型别名。它们都是不区分大小写的,具体参见官方文档

    https://mybatis.org/mybatis-3/zh/configuration.html#properties

3.6 映射器(mappers)

​ 既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 file:/// 形式的 URL),或类名和包名等。

我们主要用3种方式来引入mapper文件:

  • 方式1,使用相对于类路径的资源引用

    <mappers>
      <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
      <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
      <mapper resource="org/mybatis/builder/PostMapper.xml"/>
    </mappers>
    
    

    此种方式配置灵活,没有限制条件,故推荐此种方式。

  • 方式2:通过使用映射器接口实现类的完全限定类名。

    <mappers>
      <mapper class="org.mybatis.builder.AuthorMapper"/>
      <mapper class="org.mybatis.builder.BlogMapper"/>
      <mapper class="org.mybatis.builder.PostMapper"/>
    </mappers>
    
    

    注意:

    • 接口跟Mapper配置文件必须同名。

    • 接口跟Mapper配置文件必须在同一包下。

  • 方式3:将包内的映射器接口实现全部注册为映射器。

    <mappers>
      <package name="org.mybatis.builder"/>
    </mappers>
    
    

    注意:

    • 接口跟Mapper配置文件必须同名。
    • 接口跟Mapper配置文件必须在同一包下。

​ 我们可以综合以上几种方式,mapper接口放入java中,mapper.xml放入resources目录中,但是让2者具有相同的包名跟文件名:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jCV44Odk-1623772288899)(asset\image-20200319232714799.png)]

4. XML映射器

​ MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。在上面的入门中的Mapper配置文件就是对应的一个XML映射器。我们可以把XML映射器看成DAO接口的实现。

  • ​ 每一个Mapper.xml匹配一个mapper接口,匹配的地方在namespace属性中。
  <!-- namespace:绑定一个dao/mapper中的接口,相当于mapper接口的实现 -->
<mapper namespace="com.blb.seven.dao.StudentMapper">
	......
  
</mapper>

4.1 SQL标签

  • ​ mybatis将SQL语句从JAVA代码中剥离出来,放入到mapper.xml映射器中。
<select id="selectPerson" parameterType="int" resultType="hashmap">
  SELECT * FROM PERSON WHERE ID = #{id}
</select>

​ 共有4中SQL标签:insert、update、delete、select。

在SQL标签中有一些重要的属性需要我们来熟练掌握。

  • id :对应的是mapper接口中的方法名,表示实现哪个方法的业务。

  • parameterType:定义调用方法参数类型,如果简单类型或者基本类型可以不用显示给出,mybatis的类型处理器会自动识别。如果参数过多可以放入map中作为参数传递。

    如果方法有多个参数的时候,使用@Param注解取名。也可以通过arg0 ……argN 或者 param1 …… paramN来分别表示参数列表的第几个位置的参数。推荐使用@Param。

  • resultType:指定返回的结果类型。

  • resultMap:对外部 resultMap 的命名引用。 resultType 和 resultMap 之间只能同时使用一个。

  <!--  #{}表示值,跟方法中的形参列表中的名称一致 ,parameterType参数类型,resultType结果类型-->
  <select id="queryUserById" parameterType="int" resultType="com.seven.demo.bean.User">
    select * from student where s_id = #{userId}
  </select>

4.2 resultMap

resultMap 元素是 MyBatis 中最重要最强大的元素。主要用来把数据库中的字段跟java中的属性按照我们的意图来映射。当我们数据库字段跟属性不一样时我们可以把这种映射关系在resultMap中来手动一一映射。

<!-- 定义一个resultMap标签,把数据库字段跟type对应的类型的属性进行映射 -->
	<resultMap type="com.blb.seven.bean.User" id="stuResultMap">
		<id property="id" column="s_id"   /><!-- 把s_id字段跟id属性进行映射 -->
		<result property="sName"  column="s_name"  /><!-- 把s_name字段跟sName属性进行映射 -->
	</resultMap>
 
  <!-- resultMap的值指向上面定义的resultMap标签的id属性值 -->
  <select id="queryUserById"  resultMap="stuResultMap" >
    select * from student where s_id = #{userId}
  </select>

  • idresult 元素都将一个列的值映射到一个简单数据类型(String, int, double, Date 等)的属性或字段。这两者之间的唯一不同是,id 元素对应的属性会被标记为对象的标识符,在比较对象实例时使用。 这样可以提高整体的性能。

5. 注解SQL

5. 1 注解SQL

​ 之前的SQL我们是在mapper.xml文件中来完成映射的,还有另一种方法来完成语句映射。它们映射的语句可以不用 XML 来配置,而可以使用 Java 注解来配置。比如:

	@Select(" select * from student where s_id = #{userId}  ")
	public User queryUserById(int userId);

​ 使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。

5.2 字符串替换

​ 默认情况下,使用 #{} 参数语法时,MyBatis 会创建 PreparedStatement 参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)。 这样做更安全,更迅速,通常也是首选做法,不过有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串。 比如 ORDER BY 子句,这时候你可以:ORDER BY ${columnName}.这样,MyBatis 就不会修改或转义该字符串了。

​ 当 SQL 语句中的元数据(如表名或列名)是动态生成的时候,字符串替换将会非常有用。 举个例子,如果你想 select 一个表任意一列的数据时,不需要这样写:

@Select("select * from user where id = #{id}")
User findById(@Param("id") long id);

@Select("select * from user where name = #{name}")
User findByName(@Param("name") String name);

@Select("select * from user where email = #{email}")
User findByEmail(@Param("email") String email);

而是可以只写这样一个方法:

@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);

这种方式也同样适用于替换表名的情况。

​ 用这种方式接受用户的输入,并用作语句参数是不安全的,会导致潜在的 SQL 注入攻击。因此,要么不允许用户输入这些字段,要么自行转义并检验这些参数。

6. 日志

6.1 STDOUT_LOGGING

  • 在核心配置文件中增加如下设置选项则可以打开日志选项:
<setting name="logImpl" value="STDOUT_LOGGING"/>

tip: 注意大小写要完全一样,不能有多的空格。

此时我们再运行代码则会有如下效果[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q114P45W-1623772288900)(asset\image-20200317231533992.png)]

6.2 log4j

​ Log4j是apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX、Syslog、守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

  • 在核心配置文件中增加如下设置选项则可以打开日志选项:
<setting name="logImpl" value="LOG4J"/>

  • 引入依赖
  <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.17</version>
  </dependency>

  • 在资源目录新建log4j的配置文件(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/seven.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=error
  log4j.logger.java.sql=DEBUG
  log4j.logger.java.sql.Statement=DEBUG
  log4j.logger.java.sql.ResultSet=DEBUG
  log4j.logger.java.sql.PreparedStatement=DEBUG

  • 手动使用log4j
Logger logger = Logger.getLogger(StudentMapperTest.class);
logger.debug("log4j: debug ... ");
logger.info("log4j: info ... ");
logger.warn("log4j: warn ... ");
logger.error("log4j: error ... ");

7. 分页

7.1 传统方式

  • Mapper接口中添加方法:

    public List<User>  queryAllUserLimit(Map<String,Object> map);
    
    
  • Mapper.xml映射

    <resultMap type="com.blb.seven.bean.User" id="stuResultMap">
        <id property="id" column="s_id"   />
        <result property="sName"  column="s_name"  />
    </resultMap>
     
     <select id="queryAllUserLimit" parameterType="map"  resultMap="stuResultMap">
     	select * from student limit #{pageStart} , #{pageSize}
     </select>
    
    

    由于方法参数有多个,为了方便我们把参数定义成Map。

  • 测试

    	@Test
    	public void testQueryAllUserLimit() throws IOException{
    		
    		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    		
    		try (SqlSession session = sqlSessionFactory.openSession();){
    			StudentMapper studentMapper = session.getMapper(StudentMapper.class);
    //			将分页需要的2个参数放入map集合中作为参数传入
    			Map< String , Object> map = new HashMap< String , Object>();
    			map.put("pageStart", 0);
    			map.put("pageSize", 3);
    			List<User> users = studentMapper.queryAllUserLimit(map);
    			System.out.println(users);
    		}
    		
        }
    
    

7.2 RowBounds方式(不推荐)

  • Mapper接口中添加方法:

    public List<User>  queryAllUserLimitRowBounds();
    
    
  • Mapper.xml映射

<resultMap type="com.blb.seven.bean.User" id="stuResultMap">
		<id property="id" column="s_id"   />
		<result property="sName"  column="s_name"  />
	</resultMap>

 <!-- sql中不需要limit部分 -->
 <select id="queryAllUserLimitRowBounds" resultMap="stuResultMap">
 	select * from student 
 </select>

  • 测试

    	@Test
    	public void testQueryAllUserLimitRowBounds() throws IOException{
    		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
    		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    		
    		try (SqlSession session = sqlSessionFactory.openSession();){
    //			分页参数都封装到RowBounds对象中
    			RowBounds rowBounds  = new RowBounds(0,3);
    			List<User> users = session.selectList("com.blb.seven.dao.StudentMapper.queryAllUserLimitRowBounds", null, rowBounds);
    			System.out.println(users);
    		}
    	}
    
    

7.3 分页插件

​ MyBatis 分页插件 PageHelper

  • 官网:https://pagehelper.github.io/
  • github: https://github.com/pagehelper/Mybatis-PageHelper

8. 复杂查询

8.1 数据准备

​ 在数据库中准备2张表(学生表跟老师表)跟一些测试数据:

CREATE TABLE `student` (
  `s_id` int(11) NOT NULL AUTO_INCREMENT,
  `s_name` varchar(10) DEFAULT NULL,
  `s_pwd` varchar(10) DEFAULT NULL,
  `t_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`s_id`),
  KEY `t_f` (`t_id`),
  CONSTRAINT `t_f` FOREIGN KEY (`t_id`) REFERENCES `teacher` (`t_id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES ('1', 'zhangsan', '111', '1');
INSERT INTO `student` VALUES ('2', 'lisi', '222', '2');
INSERT INTO `student` VALUES ('3', 'wangwu', '333', '3');
INSERT INTO `student` VALUES ('4', 'zhaoliu', '444', '1');
INSERT INTO `student` VALUES ('5', 'tianqi', '555', '2');
INSERT INTO `student` VALUES ('6', 'wangba', '666', '3');
 

-- ----------------------------
-- Table structure for teacher
-- ----------------------------
DROP TABLE IF EXISTS `teacher`;
CREATE TABLE `teacher` (
  `t_id` int(11) NOT NULL AUTO_INCREMENT,
  `t_name` varchar(10) DEFAULT NULL,
  PRIMARY KEY (`t_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of teacher
-- ----------------------------
INSERT INTO `teacher` VALUES ('1', 'apple');
INSERT INTO `teacher` VALUES ('2', 'seven');
INSERT INTO `teacher` VALUES ('3', 'lili');

8.1 多对一

8.1.1 实体类
public class Student {
	
	private int id  ;
	
	private String sName = "" ;
	
	private String sPwd = "" ;
	
	private Teacher teacher ;
    
 	//setter and getter   
}

public class Teacher {
	
	private int tId ;
	
	private String tName = "" ;
    
  	//setter and getter   
}  

8.1.2 Mapper接口
public interface StudentMapper {
		
	public List<User>  queryStudentWithTeacher();
	
}

8.1.3 Mapper.xml
  • 方式1:
<?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.blb.seven.dao.StudentMapper">
 
	<resultMap type="Student" id="StudentTeacher">
		<id property="id" column="s_id"   /> 
		<result property="sName"  column="s_name"  /> 
		<result property="sPwd"  column="s_pwd"  /> 
		<!-- 如果是一个对象用association,集合用collection  -->
		<association property="teacher" javaType="Teacher" column="{hello_id=t_id}" select="getTeacher" />
	</resultMap>
 
 
 <select id="queryStudentWithTeacher" resultMap="StudentTeacher">
 	select * from student 
 </select>
 
 <select id="getTeacher" resultType="Teacher">
 	select * from teacher where t_id= #{hello_id}
 </select>
  
</mapper>

tip:

  • 首先执行queryStudentWithTeacher方法对应的select,结果Map映射为StudentTeacher。只有teacher这个字段需要处理,把查出来的结果t_id字段放入第2个sql中查询,结果封装成Teacher对象再映射。

  • column="{hello_id=t_id}"表示把查询出来的t_id字段值取名为hello_id,在下面的getTeacher的sql中直接引用。由于这里只有一列,可以简写成column=“t_id”,则getTeacher的sql中的参数可所以给名字。

  • 如果有多列需要传递column="{arg1=column1,arg2=column2,…}"。

  • 方式2:

<?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.blb.seven.dao.StudentMapper">

	<resultMap type="Student" id="StudentTeacher">
		<!-- 先从结果封装Student的属性 -->
		<id property="id" column="s_id"   /> 
		<result property="sName"  column="s_name"  /> 
		<result property="sPwd"  column="s_pwd"  /> 
		<!-- 再把Teacher的字段取出配置成teacher属性 -->
		<association property="teacher"  javaType="Teacher" >
			<id property="tId" column="t_id"   /> 
			<result property="tName"  column="t_name"  /> 
		</association>
	</resultMap>
	

	<select id="queryStudentWithTeacher" resultMap="StudentTeacher" >
		select s_id,s_name,s_pwd,teacher.t_id,t_name from student ,teacher where student.t_id = teacher.t_id ;
	</select>

</mapper>

8.1.4 测试
@Test
	public void testQueryStudents() throws IOException{
		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		try (SqlSession session = sqlSessionFactory.openSession();){
			StudentMapper studentMapper = session.getMapper(StudentMapper.class);
			List<Student> user = studentMapper.queryStudentWithTeacher();
			System.out.println(user);
		}
	}

8.2 一对多

  • 一个老师对应N个学生,查询某个老师的所有信息,包括所属的学生信息。
8.2.1 实体类
public class Student {
	
	private int id  ;
	
	private String sName = "" ;
	
	private String sPwd = "" ;
	// 现在是老师对应学生,这个就直接定义简单类型即可
	private int tId ;
    
 	//setter and getter   
}

public class Teacher {
	
	private int tId ;
	
	private String tName = "" ;
    
    private List<Student> students ;
    
  	//setter and getter   
}  

8.2.2 Mapper接口
public interface StudentMapper {
	
	//	查询某一个老师的所有信息以及所属的学生信息
	public Teacher  queryTeacher(@Param("t_id") int teacherId);
	
}

8.2.3 Mapper.xml
  • 方式1:
  <?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.blb.seven.dao.StudentMapper">
  
  <select id="queryTeacher" resultMap="teacherMap" >
  	select * from teacher where t_id = #{t_id}
  </select>
  
  <resultMap type="Teacher" id="teacherMap" >
  	<id property="tId" column="t_id"/>
  	<result property="tName" column="t_name"/>
  	<!-- javaType是定义类型, ofType是定义集合泛型的类型-->
  	<collection property="students" column="{hello_id=t_id}" javaType="ArrayList" ofType="Student" select="getStudents" />
  </resultMap>
  
  <select id="getStudents" resultMap="StudentMap" >
  	select * from student where t_id= #{hello_id}
  </select>
  <!-- 这里定义StudentMap主要是因为属性中叫id,字段名叫s_id  -->
  <resultMap type="Student" id="StudentMap">
  	<id property="id" column="s_id" />
  </resultMap>
  
  </mapper>

  • 方式2:
<?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.blb.seven.dao.StudentMapper">


	<resultMap type="Teacher" id="teacherMap">
		<id property="tId"  column="t_id"  />
		<result property="tName"  column="t_name" />
		<!-- 定义类型使用javaType,集合泛型中的类型用ofType -->
		<collection property="students" ofType="Student"   >
			<id property="id" column="s_id" />
			<result property="tId"  column="t_id" />
			<result property="sName"  column="s_name" />
			<result property="sPwd"  column="s_pwd" />
		</collection>
	
	</resultMap>

	<select id="queryTeacher" resultMap="teacherMap">
		select s_id,s_name,s_pwd,teacher.t_id,t_name from student ,teacher
		 where student.t_id = teacher.t_id and teacher.t_id = #{t_id};
	</select>

</mapper>

8.2.4 测试
	@Test
	public void testQueryTeacher() throws IOException{
		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		try (SqlSession session = sqlSessionFactory.openSession();){
			StudentMapper studentMapper = session.getMapper(StudentMapper.class);
			Teacher teacher = studentMapper.queryTeacher(2);
			System.out.println(teacher);
		}
	}

8.3 懒加载

​ 当多表进行关联查询的时候比如student表跟teacher表一起全部查出来是比较耗时的,而且当我们需要查看student信息的时候根本不用去查teacher信息。所以此时,我们使用分步查询+懒加载的优势就体现出来了。

​ 懒加载的意思就是按照需要加载。当查询student信息的时候只执行select * from student ,但当我们使用到里面的teacher信息时才去执行select * from teacher where t_id = #{t_id}。当数据量大的时候这种优势就自体现出来了。

​ 懒加载只对关联查询起作用(一对一、一对多、多对多) 。

​ 默认 equals,clone,hashCode,toString 这些方法触发懒加载,可以在 lazyLoadTriggerMethods的设置中配置。

​ 使用步骤:

  • 打开全局懒加载开关,则所有的关联查询都会进行懒加载。
<setting name="lazyLoadingEnabled" value="true"/>

  • 在分步查询过程的resultMap映射中设置 fetchType=“lazy” ,这是开关会覆盖上面全局的懒加载开关.
<?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.blb.seven.dao.StudentMapper">
 
 	<resultMap type="Student"   id="studentTeacherMap">
 		<id property="id"  column="s_id"/>
 		<result   property="sName"  column="s_name" />
 		<result   property="sPwd"  column="s_pwd" />
 		<association property="teacher" fetchType="lazy" column="{hello_id=t_id}"  javaType="Teacher"  select="queryTeacherById"    />
 	</resultMap>
 
 
 	<select id="queryStudentWithTeacher"  resultMap="studentTeacherMap">
 		select * from student 
 	</select>
 	
 	<select id="queryTeacherById"  resultType="Teacher">
 		select * from teacher where t_id = #{hello_id}  
 	</select>
  
  
</mapper>

  • 测试1
		try (SqlSession session = sqlSessionFactory.openSession();) {
			StudentMapper studentMapper = session
					.getMapper(StudentMapper.class);
			List<Student> ours = studentMapper.queryStudentWithTeacher();
//			这里只打印学生的姓名信息,没有用到teacher信息,故不会去加载获取teacher信息的sql
			for (Student user : ours) {
				System.out.println(user.getsName() );
			}
		}

  • 结果1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gK1t5rkr-1623772288902)(asset\1585128022809.png)]

  • 测试2:把懒加载的开关关闭
  • 结果2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5HD2Se9q-1623772288903)(asset\1585129373815.png)]

9. 动态SQL

  • 要执行的SQL是根据参数的情况来动态拼接而成。

9.1 数据准备

CREATE TABLE `book` (
  `book_id` int(11) NOT NULL AUTO_INCREMENT,
  `book_name` varchar(20) DEFAULT NULL,
  `book_price` double DEFAULT NULL,
  `book_author` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`book_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of book
-- ----------------------------
INSERT INTO `book`(book_name,book_price,book_author) VALUES ( 'Java编程思想', '68', 'zhangsan');
INSERT INTO `book`(book_name,book_price,book_author) VALUES ('网页设计', '46', 'lisi');
INSERT INTO `book`(book_name,book_price,book_author) VALUES ('数据库设计', '28', 'wangwu');

9.2 实体类

public class Book {
	
	private Integer bookId = null;
	
	private String bookName = null ;
	
	private Double bookPrice = null;
	
	private String bookAuthor = null ;
 
    // setter and getter
}

9.3 Mapper接口

public interface BookMapper {
 	 
	 public int insertBook(Book book);
	 
	 public List<Book> queryBookWithLike(Map map);
	
}

9.4 Mapper.XML

<mapper namespace="com.blb.seven.dao.BookMapper">

<insert id="insertBook" parameterType="Book">
	insert into book(book_name,book_price,book_author) VALUES(#{bookName},#{bookPrice},#{bookAuthor}) 
</insert>


<select id="queryBookWithLike" resultType="Book">
	select * from book where  
    <!-- if条件判断,如果参数nameKeyWord有内容的话,则拼接标签内的SQL -->
	<if test="nameKeyWord != null">
		book_name like #{nameKeyWord}
	</if>
    
	<!-- if条件判断,如果参数authorKeyWord有内容的话,则拼接标签内的SQL -->
	<if test="authorKeyWord != null">
		and book_author like #{authorKeyWord}
	</if>
</select>

</mapper>

  • 这里最终的SQL跟条件(authorKeyWord != null)是否成立有关。

9.5 测试

	@Test
	public void testqueryBookWithLike() throws IOException{
		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		try (SqlSession session = sqlSessionFactory.openSession();){
			BookMapper bookMapper = session.getMapper(BookMapper.class);
//			参数传递
			Map<String,Object> map = new HashMap<String,Object>();
			map.put("nameKeyWord", "%计%");
			map.put("authorKeyWord", "%w%");
			
			List<Book> bookList = bookMapper.queryBookWithLike(map);
			 for (Book book : bookList) {
				System.out.println(book);
			}
		}
	}

​ 通过上面的案例我们可以看到实际上执行的SQL是根据参数的情况而动态生成的,在Mapper.XML进行了非空验证。而这个非空验证是通过类似JSTL的标签来使用的,非常方便好用。

9.6 if

通过条件的是否成立来决定SQL的拼接,通过上面的案例可以看到当test判断结果为true的时候才将标签内的SQL拼接,否则不进行拼接。

<select id="queryBookWithLike" resultType="Book">
	select * from book where book_name like #{nameKeyWord} 
	<!-- if条件判断,如果参数authorKeyWord有内容的话,则拼接标签内的SQL -->
	<if test="authorKeyWord != null">
		and book_author like #{authorKeyWord}
	</if>
</select>

9.7 choose、when、otherwise

<select id="queryBookWithLike" resultType="Book">
	select * from book where  
	<choose>
		<when test="nameKeyWord != null">
			and book_name like #{nameKeyWord} 
		</when>
		<when test="authorKeyWord != null ">
			and book_author like #{authorKeyWord}
		</when>
		<otherwise>
			and book_price > 30
		</otherwise>
	</choose>
	 
</select>

	@Test
	public void testqueryBookWithLike() throws IOException{
		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		try (SqlSession session = sqlSessionFactory.openSession(true);){
			BookMapper bookMapper = session.getMapper(BookMapper.class);

			Map<String,Object> map = new HashMap<String,Object>();
//			map.put("nameKeyWord", "%计%");
			map.put("authorKeyWord", "%w%");
			
			List<Book> bookList = bookMapper.queryBookWithLike(map);
			 for (Book book : bookList) {
				System.out.println(book);
			}
		}
    }

​ 这里的choose、when、otherwise中的意义相同。类似java中的 switch case块。

9.8 where

我们来回顾下9.4中的案例:

<select id="queryBookWithLike" resultType="Book">
	select * from book where  
    <!-- if条件判断,如果参数nameKeyWord有内容的话,则拼接标签内的SQL -->
	<if test="nameKeyWord != null">
		 book_name like #{nameKeyWord}
	</if>
    
	<!-- if条件判断,如果参数authorKeyWord有内容的话,则拼接标签内的SQL -->
	<if test="authorKeyWord != null">
		and book_author like #{authorKeyWord}
	</if>
</select>

  • ​ 在这个案例中,如果nameKeyWord、authorKeyWord都是null的话,则最终的SQL为select * from book where。最后会多出一个where导致结果错误。

  • ​ 如果nameKeyWord为null,而authorKeyWord不为null的话最终SQL为select * from book where and book_author like #{authorKeyWord},这里where后面会多出一个and导致SQL错误。

那么如何优化类似这种情况呢?答案是通过我们的where标签。将上面的SQL优化下:

  <select id="queryBookWithLike" resultType="Book">
  	select * from book 
  	<where>
  		 <!-- if条件判断,如果参数nameKeyWord有内容的话,则拼接标签内的SQL -->
  		<if test="nameKeyWord != null">
  			 book_name like #{nameKeyWord}
  		</if>
  		   
  		<!-- if条件判断,如果参数authorKeyWord有内容的话,则拼接标签内的SQL -->
  		<if test="authorKeyWord != null">
  			and book_author like #{authorKeyWord}
  		</if>
  	</where>
  	
  </select>

  • 在这里我们把SQL中的where改成了标签,这个where标签就能动态帮我们处理上面遇到的问题:where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

9.9 set

我们再来看下修改操作,按照我们之前的写法如下:

<update id="updateBook"  parameterType="Book">
	update book set 
	<if test="bookName!=null">book_name = #{bookName}, </if>	
	<if test="bookPrice!=null">book_price = #{bookPrice}, </if>	
	<if test="bookAuthor!=null">book_author = #{bookAuthor} </if>	
	where book_id = #{bookId}

</update>

@Test
	public void testUpdateBook() throws IOException{
		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		
		try (SqlSession session = sqlSessionFactory.openSession(true);){
			BookMapper bookMapper = session.getMapper(BookMapper.class);
			// 传入要修改的book对象
			Book book = new Book();
			book.setBookId(4);
			book.setBookName("MYSQL入门到放弃");
			book.setBookPrice(55);
			book.setBookAuthor("hello");
			int i = bookMapper.updateBook(book);
			if(i>0){
				System.out.println("修改成功。");
			}
		}
	}

​ 这样的话,我们确实可以修改成功。但是修改的字段是由用户决定的, 如果用户修改的字段不包括作者名呢?也就是去掉book.setBookAuthor(“hello”);这句内容。则拼接的SQL为update book set book_name = ?, book_price = ?, where book_id = ?,在这个SQL中where前会多出一个,号。不同的修改字段的组成会让你对这个,号的处理异常麻烦,这时我们优化如下:

<update id="updateBook"  parameterType="Book">
	update book 
	<set> 
		<if test="bookName!=null">book_name = #{bookName}, </if>	
		<if test="bookPrice!=null">book_price = #{bookPrice}, </if>	
		<if test="bookAuthor!=null">book_author = #{bookAuthor} </if>	
	</set>
	where book_id = #{bookId}

​ 这里我们使用了set标签代替了set,这个set标签会自动帮我们处理麻烦的,号。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。

tip:

  • 由于我们是根据字段是否为null来拼接SQL,所以Book的bean中的字符串字段不能初始化空串。
  • 也不能有基本数据类型,都改成对应的包装类。

9.10 trim

如果上面的where跟set不太符合我们的需求,我们还可以通过trim来DIY。

trim标签有4个重要的属性:

属性描述
prefix给sql语句拼接的前缀
suffix给sql语句拼接的后缀
prefixOverrides去除sql语句前面的关键字或者字符,该关键字或者字符由prefixOverrides属性指定
suffixOverrides去除sql语句后面的关键字或者字符,该关键字或者字符由suffixOverrides属性指定
  • where 元素等价的自定义 trim 元素为:
<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

tip:prefixOverrides中的AND后面跟OR后面的空格不要省略。

  • set 元素等价的自定义 trim 元素为:
  <trim prefix="SET" suffixOverrides=",">
    ...
  </trim>

9.11 foreach

​ foreach是动态SQL中比较容易出错的标签。主要用来迭代拼接SQL的。 foreach元素的属性主要有item,index,collection,open,separator,close。 分别表示

  • item: 集合中元素迭代时的别名(必选)。

  • index: 在list和数组中,index是元素的序号,在map中,index是元素的key(可选)。

  • open:foreach代码的开始符号,一般是(和close=")"合用。常用在in(),values()时(可选)。

  • close: foreach代码的关闭符号,一般是)和open="("合用。常用在in(),values()时(可选)。

  • separator:元素之间的分隔符,例如在in()的时候,separator=","会自动在元素中间用“,“隔开,避免手动输入逗号导致sql错误,如in(1,2,)这样。该参数可选。

  • collection: 要做foreach的对象。

    我们来看一个案例,加入我们想拼接的SQL为select * from student where s_id in ( 1 ,2 ,3 )时,则我们需要迭代拼接的部分为 ( 1 ,2 ,3 ),这部分的开头open是“(”,结尾close是“)”。1 2 3 是迭代的集合,元素之间是用,号进分割separator。故定义如下:

  public List<User>  queryUserByIds(List<Integer> idList  );

  <select id="queryUserByIds" resultType="com.blb.seven.bean.User"  >
  	select * from student where s_id in	
  	 <foreach collection="list" open="(" close=")" separator=","  item="id"  index="i">
            #{id}
       </foreach> 
  </select>

  UserMapper studentMapper = session.getMapper(UserMapper.class);
  List<Integer> idList = new ArrayList<Integer>();
  idList.add(1);
  idList.add(2);
  List<User> list = studentMapper.queryUserByIds(idList);

这里需要重点说明下foreach标签中的collection属性值。

  • 当参数列表是单参数且为List类型时,又没有通过@param取名时,collection属性值必须为list。因为mybatis会把list封装成一个map集合,key的值固定为“list”。

  • 当参数列表是单参数且为数组类型时,又没有通过@param取名时,collection属性值必须为array。因为mybatis会把list封装成一个map集合,key的值固定为“array”。

  • 当参数列表封装到map的时候,则collection属性值没有固定值,直接设置map的key即可。

  • 如果不想使用mybaits定义的list或者array则需要我们通过@param来手动取名,也推荐这么做。

  • 当参数列表不是单参数的时候,必须通过@param来手动取名。

9.12 SQL片段

​ 当我们SQL中有重复的SQL部分又想重复利用的时候我们可以使用SQL片段。如SQL: select s_id , s_name , s_pwd from student 。我们可以拆分成2部分:

<!-- include标签引用下面的定义SQL片段,refid属性值设置为SQL片段的id值 -->  
<select id="queryAllUser" resultType="com.blb.seven.bean.User">
    select   <include refid="col_sql"/>   from student 
  </select>
  
  <!--  sql片段,这部分SQL可以被重复使用-->
  <sql id="col_sql">
  	s_pwd,s_id ,s_name 
  </sql>

如果我们想引用的是其他Mapper中的SQL片段的话,则只需要在引入的时候把对应的mapper.xml的 namespace带上即可,如:

  <select id="queryAllUser" resultType="com.blb.seven.bean.User">
    select   <include refid="com.blb.seven.dao.UserMapper.col_sql"/>      from student 
  </select>

10.缓存

10.1 缓存简介

10.1.1 什么是缓存?
  • 存在内存中的临时数据。

  • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上查询。从缓存中查找可以提高查询效率,解决高并发系统的性能问题。

10.1.2 为什么使用缓存?
  • 减少跟数据库交互的次数,减少系统开销,提高系统效率。
10.1.3 什么样的数据使用缓存?
  • 经常查询并且不经常改变的数据。

10.2 Mybatis缓存

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

  • mybatis系统默认定义了两级缓存:一级缓存跟二级缓存。

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

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

    • 为了提高扩展性,mybaits还定义了缓存接口,我们可以来自定义二级缓存。

10.3 一级缓存

  • 一级缓存也叫本地缓存:SqlSession
  • 在同一个SqlSession会话期间相同的Mapper查询到的数据会放在本地缓存中。
  • 以后需要查询相同的数据,直接从缓存中拿,没有必要再去查询数据库。

测试:

  • 开启日志。

    <setting name="logImpl" value="STDOUT_LOGGING"/>
    
    
  • 测试在一个Sqlsession中查询2次相同的记录。

    //		必须在同一个SqlSession中。
    		SqlSession session1 = sqlSessionFactory.openSession();
    		UserMapper studentMapper = session1.getMapper(UserMapper.class);
    		
    //     第1次查询走数据库
    		User user1 = studentMapper.queryUserById(1);
    		System.out.println(user1);
    		
    		System.out.println("=================================");
    //		第2次访问获取同一个对象时直接从缓存中的user1取出来
    		User user2 = studentMapper.queryUserById(1);
    		System.out.println(user2);
    		
    //		由于user2是取的缓存中的user1,所以下面user1跟user2的equals的断言成立。
    		Assert.assertEquals(user1, user2);
    //		关闭SqlSession
    		session1.close();
    
    
  • 查看日志输出来判断。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FjI2iwIQ-1623772288904)(asset\1585064851257.png)]

总结:

  • 2次必须是查询相同的数据缓存中才会有这个对象。
  • 2次的查询必须是同一个mapper,不同的mapper不会使用缓存。
  • 如果2次中间去update/insert/delete数据,即使操作的是其它数据,那么缓存就会被清空,第2次自然也是从数据库中取。
  • 我们也可以通过sqlSession.clearCache();来手动清除缓存。
  • 一级缓存默认开始,不能关闭。

10.4 二级缓存

  • 二级缓存也叫全局缓存,由于一级缓存 作用域太低了,所以诞生了二级缓存。
  • 基于namespace级别的缓存,也就是同一个mapper对应一个二级缓存。
  • 工作机制
    • 一个会话查询的数据先放在当前的一级缓存。
    • 当会话关闭的时候一级缓存就不存在了。在关闭的时候会把缓存中的内容移到对应的namespace的二级缓存中。
    • 新的会话查询信息就会从二级缓存中获取。

使用步骤

  • 开启全局二级缓存开关
  <setting name="cacheEnabled" value="true"/>

tip: 二级全局开关是默认开启的,但为了有好的可读性最好也写上。

  • 在要使用二级缓存的mapper.xml中开启
  <cache/>

我们也可以加些参数

  <cache  eviction="FIFO"  flushInterval="60000"  size="512" readOnly="true" />

  • eviction:缓存策略

    先进先出:按对象进入缓存的顺序来移除它们。

    LRU:最近最少使用 ,移除最长时间不被使用的对象。 (默认)

  • flushInterval: 缓存保留时间

  • size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。

  • readOnly(只读)属性可以被设置为 true 或 false。 默认是false,速度上会慢一些但是会安全。

  • 将实体bean进行可序列化

    由于这个步骤会将缓存进行序列化,故实体bean需要实现Serializable接口。

    public class User implements Serializable {
    	......
    }
    
    
  • 测试

    SqlSession session1 = sqlSessionFactory.openSession();
    SqlSession session2 = sqlSessionFactory.openSession();
    
    UserMapper studentMapper = session1.getMapper(UserMapper.class);
    UserMapper studentMapper2 = session2.getMapper(UserMapper.class);
    
    
    User user1 = studentMapper.queryUserById(1);
    System.out.println(user1);
    // 只有在这个session关闭的时候才会把数据从一级缓存移到二级缓存中,故需要先close再第2次读取
    session1.close();  
    
    System.out.println("=============================");
    
    User user2= studentMapper2.queryUserById(1);
    System.out.println(user2);
    session2.close();
    
    
  • 查看结果

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Lcgfozw6-1623772288906)(asset\1585103596023.png)]

  • 补充

    • 如果2次查询中进行了insert/update/delete操作则会清空缓存,重新去数据库查询。

10.5 自定义缓存

​ 在mybatis中我们可以自定义缓存,也可以指定第三方缓存。这里我们通过ehcache演示。

  • 导包

    <dependency>
        <groupId>org.mybatis.caches</groupId>
        <artifactId>mybatis-ehcache</artifactId>
        <version>1.1.0</version>
    </dependency>
    
    
  • 在resources目录新建eacache.xml文件,内容如下:

    <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
        <!--diskStore:缓存数据持久化的目录 地址  -->
        <diskStore path=".temdir/Tmp_EhCache" />
        <defaultCache 
            <!-- maxElementsInMemory: 内存中缓存的element的最大数目-->
            maxElementsInMemory="1000" 
            <!-- maxElementsOnDisk:在磁盘上缓存的element的最大数目,默认值为0,表示不限制 -->
            maxElementsOnDisk="10000000"
            <!-- eternal: 设定缓存的elements是否过期。如果为true,则缓存的数据始终有效,如果是false,
                          要根据timeToIdleSeconds,timeToLiveSeconds判断 -->
            eternal="false" 
            <!-- overflowToDisk:内存中数据超过限制,是否需要缓存到磁盘上 -->
            overflowToDisk="false" 
            <!-- diskPersistent:是否在磁盘上持久化,重启jvm后,数据是否有效 -->
            diskPersistent="true"
            <!-- timeToIdleSeconds:对象空闲时间,只对eternal属性为false起作用  -->
            timeToIdleSeconds="120"
            <!-- timeToLiveSeconds:对象存活时间,只对eternal属性为false起作用  -->
            timeToLiveSeconds="120" 
            <!-- 磁盘缓存的清理线程运行间隔 -->
            diskExpiryThreadIntervalSeconds="120"
            <!-- memoryStoreEvictionPolicy:超过内存空间,向磁盘存储数据的策略,
                 可选:LRU 最近最少使用 FIFO 先进先出  LFU 最少使用-->
            memoryStoreEvictionPolicy="LRU" 
         />
    </ehcache>
    
    
  • 在mapper.xml中引入ehcache

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

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0yDYce64-1623772288907)(asset\1585106110605.png)]

自定义缓存

​ 那如果我们要自己写缓存而不使用第三方的缓存的话,只需要实现org.apache.ibatis.cache.Cache接口并在mapper中引入即可。需要在实现类中定义我们自己想要的缓存策略,真要自己能写出来请用10根头发来换。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值