面试题一览

第一章-MyBatis概述

帮助文档:https://mybatis.org/mybatis-3/zh/getting-started.html 视频网址:https://www.bilibili.com/video/av69742084?p=5

原理分析:https://blog.csdn.net/weixin_43184769/article/details/91126687

1什么是MyBatis/流程

MyBatis 是一个半自动化的ORM框架 (Object Relationship Mapping) –>对象关系映射

MyBatis的优点

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

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

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

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

执行流程:

2快速入门

注意:IDEA是不会编译src的java目录的xml文件,所以在Mybatis的配置文件中找不到xml文件!(也有可能是Maven构建项目的问题,网上教程很多项目是普通的Java web项目,所以可以放到src下面也能读取到)

导入jia包

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<!--  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>

<!--  idea默认不会解析 src的xml文件,所以要在maven放行-->
<build>
  <resources>
    <resource>
      <directory>src/main/java</directory>
      <includes>
        <include>**/*.xml</include>
      </includes>
    </resource>
  </resources>
 </build>

2.1mybatis-config.xml

配置文件的书写:

使用properties配置文件解耦 1编写配置文件database.properties

1
2
3
4
5
jdbc.driver=com.mysql.jdbc.Driver
#Mysql8.0需要加入时区设置  &serverTimezone=Asia/shanghai
jdbc.url=jdbc:mysql://localhost:3306/store_ssm_vue?useSSL=false&useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=123456

2在mybatis-config.xml文件中导入

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?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> 
  <!-- 1.关联数据库文件
 <context:property-placeholder location="classpath:database.properties"/>
未整合前使用下面这个,
-->
     <properties resource="database.properties" />
  
  <environments default="development"><!--环境,可以有多个environment,连接多个数据库-->
    <environment id="development">
      <transactionManager type="JDBC"/><!--事务类型,有两个jdbc/managed 托管什么也不做,交由spring管理-->
      <dataSource type="POOLED">
        <property name="driver" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
      </dataSource>
    </environment>
  </environments>

</configuration>

2.2SessionFactory

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例

创建获得session的utils类MyBatisUtils。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.lx.utils;
import com.sun.javaws.jnl.ResourcesDesc;
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;
    //静态代码块 ,加载class时就会加载代码块中代码
    static {
    //每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。 
    //SqlSessionFactory 的实例 可以通过 SqlSessionFactoryBuilder 获得。
// 而SqlSessionFactoryBuilder则可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory 的实例
        String resource = "mybatis-config.xml";
        try {
            InputStream inputStream  = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //既然有了 SqlSessionFactory,顾名思义,我们就可以从中获得 SqlSession 的实例了。
    // SqlSession 完全包含了面向数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession(true);//这是开启自动提交
    }
}

2.3Mapper.xml

需要一个接口,然后用namespace去绑定这个接口。

id指的是接口中的方法名 resultType指的是返回类型(一般写全限定类名,只写集合中的泛型)。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?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接口  dao/mapper interface-->
<mapper namespace="com.lsl.mapper.CommodityMapper">
<!--增删改查 -->

    <insert id="saveCommodity" parameterType="com.lsl.entity.Commodity">
        insert into values(null,#{cid},#{cname},#{price});
    </insert>

    <delete id="deleteCommodity" parameterType="java.lang.Integer">
        delete from commodity where id=#{id}
    </delete>

    <update id="updateCommodity" parameterType="com.lsl.entity.Commodity">
        update commodity set cid=#{cid},cname=#{cname},price=#{price} where id=#{id}
    </update>

    <select id="findCommoditys" resultType="com.lsl.entity.Commodity">
    select * from commodity
    </select>

</mapper>

获得自增长ID

1
2
3
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id" parameterType="UserEntity">
INSERT INTO USER VALUES(null,#{userName},#{password},#{realName})
</insert>

其中keyProperty的值就是数据库中自增长字段名。

执行插入后,通过get.id就可以获得自增长的id了

2.4测试

mapper标签有三个属性,resource,url,class resource是 使用相对于类路径的资源引用

1
2
3
4
在mybatis-config.xml加入,把接口实现的Mapper写到配置文件中
<mappers>
    <mapper resource="com/lx/dao/AccountMapper.xml"/>
</mappers>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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.Reader;

public class MyBatisUtil {

    private static SqlSessionFactory sqlSessionFactory;

    //静态代码块,加载class时候就会加载
    static{
        try {
            Reader reader= Resources.getResourceAsReader("mybatis-config.xml");
            sqlSessionFactory=new SqlSessionFactoryBuilder().build(reader);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession(true);//这是开启自动提交
    }
}

//测试 需要MyBatisUtils
@Test
public void selectAllAccount() {
    SqlSession sqlSession= MyBatisUtils.getSqlSession();
    //执行SQl
    AccountDao accountDao=sqlSession.getMapper(AccountDao.class);
    List<Account> list=accountDao.selectAllAccount();
    for(Account account:list){
        System.out.println(account);
    }
    sqlSession.close();
}

2.5常见错误

Error parsing SQL Mapper Configuration. Cause: java.io.IOException: Could not find resource com/lx/dao/AccountMapper.xml

无法找到mapper.xml,要么是Mappper的名称路径错误,或者是IDEA的问题。 IDEA的maven项目中,默认源代码目录下(src/main/java目录)的xml等资源文件并不会在编译的时候一块打包进classes文件夹,而是直接舍弃掉。如果使用的是Eclipse,Eclipse的src目录下的xml等资源文件在编译的时候会自动打包进输出到classes文件夹。

方式一:pom.xml文件中,对资源进行释放

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>

    </resources>
</build>

方式二:增加classpath:

例如在springboot项目中,在application.properties中增加: mybatis.mapper-locations=classpath:mapper/*.xml

3CRUD

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<mapper namespace="com.lsl.mapper.CommodityMapper">
    <insert id="saveCommodity" parameterType="com.lsl.entity.Commodity">
        insert into values(null,#{cid},#{cname},#{price});
    </insert>

    <delete id="deleteCommodity" parameterType="java.lang.Integer">
        delete from commodity where id=#{id}
    </delete>

    <update id="updateCommodity" parameterType="com.lsl.entity.Commodity">
        update commodity set cid=#{cid},cname=#{cname},price=#{price} where id=#{id}
    </update>

    <select id="findCommoditys" resultType="com.lsl.entity.Commodity">
    select * from commodity
    </select>
</mapper>

namespace

mapper中的namespace,用来写接口的全限定命名。映射文件中的namespace是用于绑定Dao接口的即面向接口编程

之前用session.selectList(“com.lx.dao.类名.方法名”)这样可以为每个sql语句起一个唯一标识的作用,避免在以后使用时可能导致的混乱。 现在使用session查询时,是用session.getMapper(xxdao.class)通过接口来获得实现类,就需要让接口和实现类进行绑定,就是通过namespace进行的,这样更加的方便,减少了全限定名称的出错。

**id:**对应namespace中接口的方法名。 **resultType:**sql语句执行的返回类型,当是集合时,只用指定泛型就好。 **parameterType:**参数的类型,(似乎可以不用写。)

3.1查询

==注意,%应该使用双引号”“==

模糊查询:%百分号可以在传值时传入%王%,也可以在mapper中写。

1
2
3
<select id="selectAccountByLikeAccount" resultType="com.lx.vo.Account">
    select * from account where account like "%"#{account}"%"
</select>

查询条件:1个.如:id dao方法接口:Account selectAccountById(Integer id); mapper.xml:#{可以写任意值},不用写参数类型。#意思是:当作字符串,避免sql注入

1
2
3
<select id="selectAccountById" resultType="com.lx.vo.Account" >
  select * from account where aid =#{aid}
  </select>

时间类型的查询,可以直接使用String

查询条件多个

方法一:@Param(“account”)直接设置参数

dao方法接口:List selectAccountByIdAndAccount( @Param(“account”) String account,@Param(“aid”)Integer aid); 其中:@Param(“account”) String account 表明括号的参数,对应mapper中的参数为account

1
2
3
<select id="selectAccountByIdAndAccount" resultType="com.lx.vo.Account" >
select * from account where account =#{account} and aid=#{aid}
</select>
  • parameterType="全限定类名”,这样也可以,就不用一个一个设置参数了。

方式二:用Map<String,Object>设置参数

dao方法接口:List selectByAccountAndStatus(Map<String,Object> map);

1
2
3
<select id="selectAccountByAccountAndStatus" parameterType="map" resultType="com.lx.vo.Account">
  select * from account where account =#{account} and status=#{status}
</select>

调用时候:需要map.put(“参数名称”,参数值);如:put(“aid”,1019) 当查询方法写的多个参数时,但map为null或者参数不足时,查询结果为Null

3.2分页

1
List<Account> selectAllAccountByPage(Map<String,Object> map);//分页查询所有
1
2
3
<select id="selectAllAccountByPage" parameterType="map" resultType="com.lx.vo.Account">
select * from account limit #{startIndex},#{pageSize}
</select>

不同数据库,查询语句各不相同。limit 开始下标(从0开始),查询几条。

3.3增删改

添加

dao方法接口:Integer insertAccount(Account account);//插入一个account

注意:增删改时要session.commit()。否则不会执行,但id会自动增长一个。 可以在用seesionFactory.getSession(true)时候设置自动提交。

增删改时,用的标签不同。<delete><update>

1
2
3
<insert id="insertAccount" parameterType="com.lx.vo.Account">
    insert into account(aid,account,password,status) values(null,#{account},#{password},#{status})
</insert>

注意:若想使用主键自增<insert id="insert" parameterType="student" keyProperty="studentId" useGeneratedKeys="true">

3.4注解CRUD

简单的sql可以用注解,更加快速。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 @Select("select * from account")
List<Account> getAllAccountAnnotation();//查询

@Insert("insert into values(null,#{account},#{password},#{status})")
int addAccountAnnottation(Account account);//添加  使用#号防止sql注入,相当于prepareStatement

@Update("update account set account=#{account},password=#{password},status=#{status} where aid=#{aid}")
int updateAccountAnnotation(Account account);//修改

@Delete("delete from account where aid=#{aid}")
int deleteAccountAnnotation(Integer aid);//删除

3.5表名和类属性名不一致

原因:在查询数据时候,查询的是password,而返回类型为Account,在实体类中找不到password,无法进行赋 值。

用ResultMap解决, resultMap="自定义的resultMap的id”。 <resultMap id="staffMap" type="Staff"> type指resultMap映射的类型,property,是属性名,Column表的字段名。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<select id="getStaffById" resultMap="staffMap">
select * from staff where id=#{id}
</select>
<resultMap id="staffMap" type="Staff">
    <result property="id" column="id"></result> <!--property指的是VO类中的属性,column是数据库表的字段-->
    <result property="name" column="name"></result>
    <result property="age" column="age"></result>
    <result property="birth" column="birth"></result>
    <result property="department" column="department"></result>
</resultMap>

简单解决:起别名 。在Mapper中sql语句起别名解决。 a.password pwd

1
2
3
4
5
6
public class Account {
   private Integer aid;
   private String account;
   private String pwd;//在数据库中的字段为password,此时查询为null
   private String status; 
}
1
2
3
4
<mapper namespace="com.lx.dao.AccountDao">
    <select id="selectAllAccount" resultType="Account">
    select a.aid,a.account,a.password pwd,a.status from account a
    </select>

4mybatis.xml配置解析

configuration(配置):myBatis配置文件中的标签,需要掌握一下三个部分。

  1. properties 可以配置在Java 属性配置文件中 settings 修改 MyBatis 在运行时的行为方式 typeAliases 为 Java 类型命名一个短的名字

  2. environments 环境

  • environment 环境变量
    • transactionManager 事务管理器
    • dataSource 数据源
  1. mappers l 映射器

4.1properties/settings/typeAliases

1 properties 可以配置在Java 属性配置文件中,引入db.properties文件进行配置,也可以在内部标签进行配置,若有同名属性优先使用外部配置。 settings 修改 MyBatis 在运行时的行为方式,有很多设置,详情可以看官方文档。 typeAliases 为 Java 类型命名一个短的名字 ,package扫描这个包下的bean类,并为其起别名(默认类名首字母 小写),可以用@Alias注释手动设置别名,或者用typeAlias属性设置。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
<!--引入外部配置文件-->
    <properties resource="database.properties" />

<settings>
 <setting name="logImpl" value="STDOUT_LOGGING打印语句/LOG4J"/>指定日志
 <setting name="mapUnderscoreToCamelCase" value="true/false"/>是否开启驼峰命名转换(数据库t_id转为tId,实体类tId转为t_id)
 <setting name="cacheEnabled" value="true"/>开启缓存(全局性地开启或关闭所有映射器配置文件中已配置的任何缓存),默认开启
 <setting name="log-impl" value="org.apache.ibatis.logging.stdout.StdOutImpl"/>打印日志
</settings>

<typeAliases>
    <typeAlias alias="de" type="com.lx.vo.Department"></typeAlias>
    <package  name="com.lx.vo"/> <!--扫描包,默认为该包下的bean类起别名,类名小写-->
</typeAliases>

4.2environments 环境

2 environments 环境 可以配置多个,但default,每次只能指定一个。 environment 环境变量 有id,可以被environments的default指定。 transactionManager 事务管理器有两种类型,JDBC/MANAGED。(JDBC - 这个类型直接全部使用 JDBC 的提交和回滚功能。它依靠使用连接的数据源来管理事务的作用域。MANAGED - 这个类型什么不做 , 它从不提交 、 回滚和关闭连接 。 而是让窗口来管理事务的全部生命周期 。(比如说 Spring 或者 JAVAEE 服务器)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为) dataSource 数据源 有三种内建的数据源类型(也就是 type=”[UNPOOLED|POOLED|JNDI]"),(UNPOOLED - 在每次请求的时打开和关闭一个连接。虽然慢,但适合不需性能和立即响应的简单应用 。 POOLED - 利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。 JNDI - 是为了准备和 Spring 或应用服务一起使用,可以在外部也可以在内部配置这个数据源,然后在 JNDI 上下文中引用它)

  • 常见数据库连接池有dbcp,c3p0,druid,HikeriCP
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<environments default="development"><!--环境,可以有多个environment,连接多个数据库-->
    <environment id="development">
        <transactionManager type="JDBC"/><!--事务类型,有两个jdbc/managed 托管什么也不做,交由spring管理-->
        <dataSource type="POOLED">
            <property name="driver" value="com.mysql.jdbc.Driver"/>
            <property name="url" value="jdbc:mysql://localhost:3306/mysql1?useUnicode=true&amp;characterEncoding=UTF-8&amp;useSSL=false"/>
            <property name="username" value="root"/>
            <property name="password" value="123456"/>
        </dataSource>
    </environment>
</environments>

4.3mapper.xml

我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。

  • 注意:dao接口和mapper.xml文件最好同名(class,package方式规定),而且必须在同一个包下。
  • 一般采用方式一。<!--注解用class xml用resource-->
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
<!-- 使用相对于类路径的资源引用 要释放资源 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
</mappers>

<!-- 使用映射器接口实现类的完全限定类名
	使用class可能会出现一些问题,它需要dao接口和mapper同名。而且需要在同一包下
 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
</mappers>

<!-- 将包内的映射器接口实现全部注册为映射器
使用包扫描和class注意点是一致的。 
-->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

5生命周期

SqlSessionFactoryBuilder

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

SqlSessionFactory

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次, 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

SqlSession

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

6MyBatis日志

logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 取值比较常用的有两个: LOG4J 、STDOUT_LOGGING(标准日志输出)

1
2
3
4
<settings>
   <!-- <setting name="logImpl" value="STDOUT_LOGGING"/>&lt;!&ndash;加上就可以使用&ndash;&gt;-->
    <setting name="logImpl" value="LOG4J"/>
</settings>

第二章-SQL语句

面对复杂的属性需要单独处理。 对象使用(多对一):<association>əˌsoʊsiˈeɪʃn 协会,联想,组合 集合使用(一对多):<collection>

1多对一

查询所有员工的部门。

实体类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Staff {
//多对一  职员需要一个部门 staff need a department
private Integer id;
private String name;
private Integer age;
private Date birth;
private Integer department;//save a department table primary id

private Department departmentVo;
}

方式一:结果嵌套查询(两个查询,推荐使用方式二,容易理解)

1
2
3
4
5
<association property="departmentVo写实体类的属性名" 
       column="department当前查询中表中和第三表连接的Id(也是要传给select的查询条件)
               当条件有多个时, column="{key传给select的括号中的命名=value当前表的字段,key=value}" " 
       javaType="Department实体类的类名"
       select="getDepartment1所对应的查询的id"/>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<mapper namespace="com.lx.dao.StaffDao">
<select id="selectAllStaffBySelect" resultMap="staffBySelectMap">
    select * from staff
</select>
    <resultMap id="staffBySelectMap" type="Staff">
        <result property="id" column="id"></result> <!--property指的是VO类中的属性,column是数据库表的字段-->
        <result property="name" column="name"></result>
        <result property="age" column="age"></result>
        <result property="birth" column="birth"></result>
        <result property="department" column="department"></result>
        <!--property是VO类属性名称,department是表中连接的id,javaType是VO属性的类型-->
        <association property="departmentVo" column="department" 
                     javaType="Department" select="getDepartment1"/>
    </resultMap>
  
    <select id="getDepartment1" resultType="Department">
/*这里传递过来的id,只有一个属性的时候,下面可以写任何值
    association中column多参数配置:
   column="{key=value,key=value}"
  其实就是键值对的形式,key是传给下个sql的取值名称,value是片段一中sql查询的字段名。*/
        select did,dname,parentDept1 from department where did =#{id}
    </select>

方式二:联表查询

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<select id="getStaffById1" resultMap="oneStaffMap">
  <!--多对一查询单个staff,方式二:联表查询-->
select s.id,s.name,s.age,s.birth,s.department,d.did,d.dname,d.parentDept1 from staff s left join department d
 on s.department=d.did where s.id=#{id}
</select>
<resultMap id="oneStaffMap" type="staff">
    <result property="id" column="id"></result> <!--property指的是VO类中的属性,column是数据库表的字段-->
    <result property="name" column="name"></result>
    <result property="age" column="age"></result>
    <result property="birth" column="birth"></result>
    <result property="department" column="department"></result>
    <association property="departmentVo" javaType="department">
        <result property="did" column="did"></result>
        <result property="dname" column="dname"></result>
        <result property="parentDept1" column="parentDept1"></result>
    </association>
</resultMap>

2一对多

实体类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {

private Integer did;
private String dname;
private Integer parentDept1;
//若是MybatisPlus @TableField(exist=false)
private List<Staff> staffs;

}

方式一:查询嵌套处理 (两个查询,推荐使用方式二,容易理解)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
<mapper namespace="com.lx.dao.DepartmentDao">
    <!--一对多 部门多个职员 方式一:查询嵌套处理 -->
    <select id="selectAllDepartment" resultMap="departmentStaffsMap">
    select * from department
    </select>
    
    <resultMap id="departmentStaffsMap" type="department">
        <!--column是一对多的外键 , 写的是一的主键的列名
						和多对一一样,column="k=v,k=v"-->
        <collection property="staffs" javaType="ArrayList" ofType="Staff"
        column="did" select="getStaffs"></collection>
    </resultMap>
    
    <select id="getStaffs" resultType="Staff">
        select * from staff where department=#{did}
    </select>

方式二:查询结果映射处理 ==property指的是实体类的did== ==column指的是查询语句的列名==

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
<select id="selectOneDepartment" resultMap="departmentMap">
select d.did,d.dname,d.parentDept1,
s.id as sid,s.name as sname,s.age as sage,s.birth as sbirth,s.department as sdepartment
from department d left join staff s on d.did=s.department where d.did=#{id}
</select>
<resultMap id="departmentMap" type="Department">
    <result property="did" column="did"></result>
    <result property="dname" column="dname"></result>
    <result property="parentDept1" column="parentDept1"></result>
    <collection property="staffs" ofType="staff">
        <result property="id" column="sid"></result>
        <result property="name" column="sname"></result>
        <result property="age" column="sage"></result>
        <result property="birth" column="sbirth"></result>
        <result property="department" column="sdepartment"></result>
    </collection>
</resultMap>

3动态SQL

3.1 where/if/set

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

用双等号

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<select id="selectAllStaffByCondition1" parameterType="map" resultType="staff">
    select * from staff
    <where>
        <if test="name != null">
            name=#{name}
        </if>
        <if test="age != null">
            and age=#{age}
        </if>
    </where>
</select>

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

1
2
3
4
5
6
7
8
<!--动态sql update-->
<update id="updateOneStaffByCondition" parameterType="map" >
    update  staff
    <set >
            <if test="name != null"> name=#{name},</if>
            <if test="age != null">  age=#{age}, </if>
    </set>where id=#{id}
</update>

3.2choose/when

想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。 otherwise都不成立

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<select id="selectAllStaffByCondition2" parameterType="map" resultType="staff">
    select * from staff
    <where >
        <choose>
            <when test="name != null"> name=#{name} </when>
            <when test="age != null"> and age=#{age} </when>
            <otherwise >and name=1</otherwise>
        </choose>
    </where>
</select>

3.3foreach/sql/trim

foreach动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候) 若参数是对象集合ArrayList<Employee>则在item使用参数时,需要item.oid

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
collection:指定输入对象中的集合类型 array list map

item:每次遍历生成的对象
open:开始遍历时的拼接字符串
close:结束时拼接的字符串
separator:遍历对象之间需要拼接的字符串
<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="dept" index="index" collection="list"
      open="(" separator="," close=")">
        #{dept.empname}
  </foreach>
</select>

sql

用<sql>和<include>标签可以重复利用sql片段。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<sql id="if_name_age">
  <if test="name != null">
    name=#{name}
  </if>
  <if test="age != null">
    and age=#{age}
  </if>
</sql>
    <!--动态sql where /set-->
<select id="selectAllStaffByCondition1" parameterType="map" resultType="staff">
    select * from staff
    <trim prefix="where" prefixOverrides="AND |or">
        <include refid="if_name_age"></include>
    </trim>
</select>

trim实际是set和where的基础。 prefix指得是前置,当trim字句中有符合条件选项时,就加上前置set。 suffixOverrides指的是后置overrides重写,去除逗号。 prefixOverrides是前置overrides重写,去除AND 或者OR

1
2
3
4
5
6
<trim prefix="SET" suffixOverrides=",">
  ...
</trim>
<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

第三章-缓存

1缓存介绍

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

2.为什么使用缓存? 减少和数据库的交互次数,减少系统开销,提高系统效率。

3.什么样的数据能使用缓存? 经常查询并且不经常改变的数据。

2MyBatis缓存

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

3一级缓存

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

缓存失效的情况: 1.查询不同的东西 2.增删改操作,可能会改变原来的数据,所以必定会刷新缓存! 3.查询不同的Mapper.xml 4.手动清理缓存!sqlSession.clearCache();

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
 StaffDao staffDao=sqlSession.getMapper(StaffDao.class);
 Map<String,Object> map=new HashMap<String,Object>();
         map.put("name","a1");
         map.put("age",3);
List<Staff> list= staffDao.selectAllStaffByCondition(map);
 list.forEach(System.out::println);
 System.out.println("==========================");
 List<Staff> list1= staffDao.selectAllStaffByCondition(map);
 list1.forEach(System.out::println);

==================下面是日志========================
[com.lx.dao.StaffDao.selectAllStaffByCondition]-==>  Preparing: select * from staff WHERE name=? or age=? 
[com.lx.dao.StaffDao.selectAllStaffByCondition]-==> Parameters: a1(String), 3(Integer)
[com.lx.dao.StaffDao.selectAllStaffByCondition]-<==      Total: 2
Staff(id=1021, name=a11, age=3, birth=Fri May 02 12:22:15 CST 1997, department=null, departmentVo=null)
Staff(id=1022, name=a1, age=3, birth=Thu May 02 12:22:15 CDT 1991, department=2, departmentVo=null)
==========================
Staff(id=1021, name=a11, age=3, birth=Fri May 02 12:22:15 CST 1997, department=null, departmentVo=null)
Staff(id=1022, name=a1, age=3, birth=Thu May 02 12:22:15 CDT 1991, department=2, departmentVo=null)

4二级缓存

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

注意:二级缓存需要实现Serializable接口。

一级缓存只存在一个sqlSession中,默认开启,如果要开启二级缓存就在映射文件mapper.xml文件中配置一个标签就好了。<cache/>

基本上就是这样。这个简单语句的效果如下: ●映射语句文件中的所有select语句的结果将会被缓存。 ●映射语句文件中的所有insert. update 和delete语句会刷新缓存。 ●缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。 ●缓存不会定时进行刷新(也就是说,没有刷新间隔)。 ●缓存会保存列表或对象(无论查询方法返回哪种)的1024个引用。 ●缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

1
2
3
4
5
6
<cache
eviction="FIFO"
flushInterval= "60000"
size="512"
readonly= "true"/>

eviction可用的清除策略有: LRU -最近最少使用:移除最长时间不被使用的对象。 FIFO -先进先出:按对象进入缓存的顺序来移除它们。 SOFT -软引用:基于垃圾回收器状态和软引用规则移除对象。 WEAK -弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。 默认的清除策略是LRU。

flushInterval (刷新间隔)属性可以被设置为任意的正整数,设置的值应该是- -个以亳秒为单位的合理时间量。默认情况是不设置, 也就是没有刷新间隔,缓存仅仅 会在调用语句时刷新。 size (引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是1024。 readOnly (只读)属性可以被设置为true或false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。 这就提供了可观的性能提 升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。速度上会慢一些, 但是更安全,因此默认值是false。

注意——-

可以在Mapper的具体方法下设置对二级缓存的访问意愿:

1
2
3
4
<select id="save"
        parameterType="XX"
        flushCache="true" 
        useCache="false"> </select>

如果没有去配置flushCache、useCache,那么默认是启用缓存的

  • flushCache默认为false,表示任何时候语句被调用,都不会去清空本地缓存和二级缓存。
  • useCache默认为true,表示会将本条语句的结果进行二级缓存。
  • 在insert、update、delete语句时: flushCache默认为true,表示任何时候语句被调用,都会导致本地缓存和二级缓存被清空。 useCache属性在该情况下没有。update 的时候如果 flushCache="false”,则当你更新后,查询的数据数据还是老的数据。