Mybatis

目录

1、简介
2、入门
3、核心文件
4、映射文件
5、动态SQL
6、注解SQL
7、缓存
8、拓展

1、简介

1.1、什么是Mybatis?

  1. MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
  2. MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象,即实体类)为数据库中的记录。
  3. Mybatis是一个半ORM(对象关系映射)框架,它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态sql,可以严格控制sql执行性能,灵活度高。
  4. MyBatis 可以使用 XML 或注解来配置和映射原生信息,将 POJO映射成数据库中的记录,避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
  5. 通过xml 文件或注解的方式将要执行的各种 statement 配置起来,并通过java对象和 statement中sql的动态参数进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射为java对象并返回。(从执行sql到返回result的过程)。

1.2、历史

MyBatis框架的前身是iBatis,是Apache软件基金会的一个开源项目。2010年,这个项目有Apache Software Foundation迁移到了Google Code,并更名位MyBatis,2013年,项目迁移到了GitHub上。

1.3、Mybaits的优点

  1. 基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。
  2. 与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接;
  3. 很好的与各种数据库兼容(因为MyBatis使用JDBC来连接数据库,所以只要JDBC支持的数据库MyBatis都支持)。
  4. 能够与Spring很好的集成;
  5. 提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。

1.4、MyBatis框架的缺点

  1. SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求。
  2. SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。
  3. 不支持方法重载!

1.5、MyBatis框架适用场合

  1. MyBatis专注于SQL本身,是一个足够灵活的DAO层解决方案。
  2. 对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis将是不错的选择。

2、入门

如果你只是想要进行简单操作,那么按照以下步骤来,即可掌握mybatis的基本用法!

  1. 导入依赖
  2. 编写核心配置文件
  3. 编写工具类
  4. 编写pojo类
  5. 编写pojo类的接口
  6. 编写映射文件
  7. 测试

2.1导入依赖

要使用 MyBatis, 只需将 mybatis-x.x.x.jar 文件置于类路径(classpath)中即可。

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

<!--这里用的是3.4.6版本,其他版本请到maven仓库自行查找-->
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>3.4.6</version>
</dependency>

2.2编写核心配置文件

  1. 导入依赖之后,需要编写一个核心配置文件(mybatis-config.xml),这个文件可以获取数据库连接,并且还可以控制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="${driver}"/>
            <property name="url" value="${url}"/>
            <property name="username" value="${username}"/>
            <property name="password" value="${password}"/>
          </dataSource>
        </environment>
      </environments>
      <mappers>
        <mapper resource="org/mybatis/example/BlogMapper.xml"/>	
      </mappers>
    </configuration>
    
  2. 在核心配置文件中,将四个property标签的value值替换成对应驱动、地址、账号、密码即可,这一步操作可以连接上数据库!

  3. mappers标签则是注册编写的接口对应的mapper.xml文件!

2.3编写工具类

  1. 你需要编写一个工具类(MybatisUtils)用来获取连接

    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();
            }
        }
        public static SqlSession getSqlSession(){
            //你可以在这里给openSession()传一个参数true,这样就无需手动提交了!
            return sqlSessionFactory.openSession();
        }
    }
    
  2. 这个工具类可以方便的获取到数据库的连接,并且充分的利用到了一些属性的作用域,使其不会占用大量资源,让系统性能降低!

  3. 其中==String resource=“mybatis-config.xml”;==这里的"mybatis-config.xml"对应的是你的核心配置文件,如果你的核心配置文件并不是这个名称,请写你自己的核心配置文件名称!

  4. getSqlSession()方法则是获取数据库的连接,它是一个静态的方法,所以可以使用类名去调用!

2.4编写pojo类

  1. 请按照你的数据库,去编写你的实体类!

2.5编写pojo类的接口

请按照你的实体类,去编写你的接口。如:

public interface StudentMapper {

    int update(Map map);

    List<Student> findAll(Map map);

    List<Student> findAll2(Map map);
    
    List<Student> findAll3(Map map);
}

这是一个学生类的接口,按照规范,它的后缀是Maaper,并且它有一个映射文件,这个文件的名称应该是StudentMapper.xml,请尽量保持接口和xml文件的名称一致,这样即便使用不同方法注册mapper.xml配置文件时,也不会出现错误,并且请将两者放到同一个包下

2.6编写映射文件

写好接口之后,你需要去创建接口的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.th.dao.StudentMapper">
  <select id="findAll" resultType="com.th.pojo.Student">
    select * from student
  </select>
</mapper>
  • 在这个配置文件中,namespace是你要映射的接口,这里映射的接口是StudentMapper,你必须使用完全限定名!
  • select标签是用来写查询语句的,其中id的值是你接口中的方法名,resultType则是你的返回类型!
  • 增删改都有自己对应的标签!它们与查询类似!
  • 编写完这个映射文件后,请到核心配置文件中的mappers标签中注册!千万不要忘记了!
  • 注意:mybatis不支持方法重载!因为id是唯一的,如果方法重载,那么方法名必定相同,两个相同的id,会出现异常!但是不同的xml,里面的方法可以同名!详细情况请阅读此文章:https://blog.csdn.net/qq_34162294/article/details/108591515

2.7测试

使用单元测试:

@Test
public void t5() {
    SqlSession sqlSession = MybatisUtils.getSqlSession();  //通过工具类获取连接
    //获取接口的映射,相当于是在实例化对象
    StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);

    Map map=new HashMap();
    map.put("tid",1);
    List<Student> list = studentMapper.findAll2(map);
    for (Student student : list) {
        System.out.println(student);
    }

    sqlSession.close();//关闭连接
}

如果是增删改查,在关闭连接前还需要进行提交事务

sqlSession.commit();	//提交事务

3、核心文件

3.1、常用的配置

configuration(配置)

  • properties(属性)
  • settings(设置)
  • typeAliases(类型别名)
  • environments(环境配置)
    • environment(环境变量)
      • transactionManager(事务管理器)
      • dataSource(数据源)
  • mappers(映射器)

常用配置仅需掌握这几个就可以,如有其他需求,请到官方文档查看其余配置!

请注意:配置文件中的这些标签是有一个顺序的,请按照顺序来,否则会报错!

3.2、properties

你可以将核心xml文件的四个连接数据库的字段放到外部配置文件中。

<!--这是核心xml文件,使用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>
//这是数据库配置文件,如果mysql版本在8.0以上,还需要加上时区
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&character=UTF-8
username=root
password=123

当然,你也可以将账号密码放入核心xml文件中,如下:

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="root"/>
  <property name="password" value="123"/>
</properties>

以上例子便是将账号密码放入xml核心配置文件中,请注意如果你在外部配置文件中也写了账号密码的属性,那么外部的配置文件会覆盖掉核心xml文件的配置!优先级:外部配置文件>核心配置文件

3.3、settings

常用的设置如下:

设置名描述有效值默认值
cacheEnabled全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。true | falsetrue
logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J | LOG4J(deprecated since 3.5.9) | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING未设置
mapUnderscoreToCamelCase是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。true | falseFalse
autoMappingBehavior指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。NONE, PARTIAL, FULLPARTIAL

一个配置完整的 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>

3.4、typeAliases

在编写mapper.xml文件时,你会发现resultType返回值写上全限定名是一件很麻烦的事情,这里,你可以给它取一个类型别名!

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

<typeAliases>
    <typeAlias type="com.th.pojo.Student" alias="student"/>
    <typeAlias type="com.th.pojo.Teacher" alias="teacher"/>
</typeAliases>

这样配置,你就可以把全限定名改成你自己定义的类别名称了!

也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

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

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

比如 com.th.pojo.Teacher 的别名为 teacher;若有注解,则别名为其注解值。

注解别名是一个很好用的东西,你也许可以尝试使用它!

@Alias("author")
public class Author {
    ...
}

还有一些为常见的 Java 类型内建的类型别名。

别名映射的类型
_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator

3.5、environments

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

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

3.6、mappers

你可以使用多种方法来注册MyBatis映射器!

推荐使用!

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

使用注解增删改查时,需要使用以下方法,但这种注册方法需要保证接口和mapper.xml文件名保持一致,否则便找不到!

<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

这种方法可以将包内的映射器全部注册!

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

以下方法并不推荐使用!

<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>

4、映射文件

4.1、顶级元素

SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):

  • cache – 该命名空间的缓存配置。
  • cache-ref – 引用其它命名空间的缓存配置。
  • resultMap – 描述查询结果集的字段和实体类属性的对应关系,是最复杂也是最强大的元素。
  • sql – 可被其它语句引用的可重用语句块。
  • insert – 映射插入语句。
  • update – 映射更新语句。
  • delete – 映射删除语句。
  • select – 映射查询语句。

4.2、单条件传参

如果仅传入一个基本类型或其他包装类型等,可以忽略掉parameterType属性,并且参数名也可以随意定义,如下:

接口:

public interface ThFinanceMapper {
    ThFinance findId(String id);
}

映射文件:

<?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.th.mapper.ThFinanceMapper">
    <select id="findId" resultType="ThFinance">
        select * from th_finance where finance_id=#{aabb}
    </select>
</mapper>

单元测试:

@Test
public void t1(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    ThFinanceMapper mapper = sqlSession.getMapper(ThFinanceMapper.class);
    ThFinance finance = mapper.findId("2022/2/24");
    System.out.println(finance.toString());
}

结果:

在这里插入图片描述

通常我们会使用==@Param()==注解,绑定参数,这样符合规范,关于@Param()将在下文讲解。

4.3、多条件传参

4.3.1、将参数作为Java对象传入

可以将java的对象作为参数传入,但是必须加上属性parameterType,并且参数名也必须与对象中的属性一致,如下:

实体类属性:

private String financeId;		//这是实体类的属性名
private String userId;
private java.sql.Timestamp financeCreateDate;
private String financePayName;
private double financePayPrice;

接口:

public interface ThFinanceMapper {
    ThFinance findThFinance(ThFinance thFinance);
}

映射文件:

<?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.th.mapper.ThFinanceMapper">
     <select id="findThFinance" resultType="ThFinance" parameterType="ThFinance">
        select * from th_finance 
        where finance_id=#{financeId} and finance_payName=#{financePayName}
    </select>
</mapper>

单元测试:

@Test
public void t2(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    ThFinanceMapper mapper = sqlSession.getMapper(ThFinanceMapper.class);
    //对象作为参数
    ThFinance thFinance = new ThFinance();
    thFinance.setFinanceId("2022/2/24");
    thFinance.setFinancePayName("早餐");
    
    ThFinance finance = mapper.findThFinance(thFinance);
    System.out.println(finance.toString());
}

结果:

在这里插入图片描述

4.3.2、将参数封装成Map对象传入

如果传入的参数很零散,并不是一个对象,那么我们可以将它们放到map集合中,把map作为参数,同样的parameterType也是必不可少的,但是需要注意的是,参数名称是map的key值:

接口:

public interface ThFinanceMapper {
    ThFinance mapThFinance(Map map);
}

映射文件:

<?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.th.mapper.ThFinanceMapper">
    <select id="mapThFinance" resultType="ThFinance" parameterType="map">
        select * from th_finance where finance_id=#{id} and finance_payName=#{name}
    </select>
</mapper>

单元测试:

@Test
public void t3(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    ThFinanceMapper mapper = sqlSession.getMapper(ThFinanceMapper.class);
    //参数名称是map的key
    Map map = new HashMap();
    map.put("id","2022/2/24");
    map.put("name","早餐");

    ThFinance finance = mapper.mapThFinance(map);
    System.out.println(finance.toString());
}

结果:

在这里插入图片描述

4.3.3、@Param传参

@Param是MyBatis所提供的(org.apache.ibatis.annotations.Param),作为Dao层的注解,作用是用于传递参数,从而可以与SQL中的的字段名相对应,一般在2=<参数数<=5时使用最佳。

使用Map传参的缺点就在于可读性差,每次必须阅读他的键,才能明白其中的作用,并且不能限定其传递的数据类型,下面是使用@Param的情况:

接口:

public interface ThFinanceMapper {
    //在这里,@Param("id")与参数financeId绑定,@Param("name")与参数payName绑定
    ThFinance paramThFinance(@Param("id") String financeId,@Param("name") String payName);   
}

映射文件:

<?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.th.mapper.ThFinanceMapper">
    
    <!--在这里,我们使用@Param所绑定的参数,并且省略parameterType-->
    
    <select id="paramThFinance" resultType="ThFinance">
        select * from th_finance where finance_id=#{id} and finance_payName=#{name}
    </select>
</mapper>

单元测试:

@Test
public void t4(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    ThFinanceMapper mapper = sqlSession.getMapper(ThFinanceMapper.class);
	
    ThFinance finance = mapper.paramThFinance("2022/2/24","早餐");
    System.out.println(finance.toString());
}

结果:

在这里插入图片描述

4.3.4、根据下标传参

除了以上几种,我们还可以根据下标传递参数,但是这种方法并不推荐使用,因为可读性要比使用map集合还要更差:

接口:

public interface ThFinanceMapper {
    //注意,这里我们并没有使用@Param
    ThFinance indexThFinance(String financeId,String payName);
}

映射文件:

<?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.th.mapper.ThFinanceMapper">
    
    <!--同样的,我们省略parameterType,但是参数却变成了下标:arg0和arg1,分别代表第一个参数和第二个参数-->
    
    <select id="indexThFinance" resultType="ThFinance">
        select * from th_finance where finance_id=#{arg0} and finance_payName=#{arg1}
    </select>
</mapper>

单元测试:

@Test
public void t5(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    ThFinanceMapper mapper = sqlSession.getMapper(ThFinanceMapper.class);

    ThFinance finance = mapper.indexThFinance("2022/2/24","早餐");
    System.out.println(finance.toString());
}

结果:

在这里插入图片描述

4.4、结果集映射

在一些复杂的查询之中,我们会遇到一个对象里面包含另一个对象,或者遇到一个对象里面包含一个集合,甚至更加复杂的结果,这个时候单凭简单的实体类对象是无法满足我们的要求的,所以我们需要进行结果集映射!

4.4.1简单理解
  • 对于复杂的结果集,都需要使用resultMap进行结果集映射
  • 它们都有两种查询处理方式:按照查询嵌套处理,按照结果嵌套处理
  • 按照查询嵌套处理:类似于子查询
  • 按照结果嵌套处理:类似于联表查询
4.4.2、相关属性或标签
association:对象里面套对象时,使用association标签

属性:

  • property:实体类中需要进行映射的属性名称
  • javaType:property属性的类型
  • select:在使用按照查询嵌套处理时,结果集映射中需要使用到select属性来指定子查询的id

子元素:

  • id:通常用来作为主键的映射,可以极大的提高Mybatis的查询效率
  • result:普通的属性映射,将实体类的property与column联系起来
collection:对象里面套集合时,使用collection标签

属性:

  • property:实体类中需要进行映射的属性名称
  • ofType:用来指定映射到List或者集合中的pojo类型,泛型中的约束类型!
  • select:在使用按照查询嵌套处理时,结果集映射中需要使用到select属性来指定子查询的id

子元素:

  • id:通常用来作为主键的映射,可以极大的提高Mybatis的查询效率
  • result:普通的属性映射,将实体类的property与column联系起来
4.4.3、一对多实例
按照结果嵌套处理
<select id="findALll" resultMap="studentTea">
    SELECT s.`name` sname,s.age sage,t.`name` tname
    from student s,teacher t
    where s.tid=t.id
</select>
<!--property是实体类的字段,column是查询出来的结果集的列名,而非表名-->
<resultMap id="studentTea" type="student">
    <result property="name" column="sname"/>
    <result property="age" column="sage"/>
    <association property="teacher" javaType="Teacher">
        <result property="name" column="tname"/>
    </association>
</resultMap>
按照查询嵌套处理
<select id="findALll" resultMap="studentTea">
    select * from student
</select>
<!--这里使用select引用了getTeacher方法-->
<resultMap id="studentTea" type="student">
    <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="teacher">
    select * from teacher where id=#{id}
</select>
4.4.4、多对一实例
按照结果套处理
<select id="getTeacher" resultMap="TeacherStu">
    select s.id sid,t.id tid,t.name tname,s.name sname,s.age sage from student s,teacher t
    where s.tid=t.id and t.id=#{id}
</select>
<resultMap id="TeacherStu" type="Teacher">
    <result property="name" column="tname"/>
    <result property="id" column="tid"/>
    <collection property="students" ofType="student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <result property="age" column="tid"/>
    </collection>
</resultMap>
按照查询嵌套处理
<select id="getTeacher" resultMap="TeacherStu">
    select * from teacher where id=#{id}
</select>
<resultMap id="TeacherStu" type="teacher">
    <collection property="students" javaType="ArrayList" ofType="student" select="getTeacherStu" column="id"/>
</resultMap>
<select id="getTeacherStu" resultType="student">
    select * from student where tid=#{tid}
</select>
4.4.5、小结
  • 我们可以在collection和association中使用resultMap进行结果集的映射,这样极大的增加了重用性!
  • 为了保证映射的正确,column属性不能重复!!!
  • resultMap和resultType的在本质上是一样的!在select中,两者不能同时使用!

4.5、自动映射

在做结果集映射时,我们需要注意的是,如果使用了collection和association,那么自动映射将会失效,我们必须手动将全部的属性和查询结果的column关联起来,当然,我们也可以在核心配置文件中设置autoMappingBehavior的属性,将它设置为FULL!

4.6、查询

<select id="findId" resultType="ThFinance">
    select * from th_finance where finance_id=#{aabb}
</select>

模糊查询:

<!--两种方式都可以-->
<select id="indexThFinance" resultType="ThFinance">
    select * from th_finance where finance_payName like "%"#{name}"%"
</select>
<select id="likeThFinance" resultType="ThFinance">
    select * from th_finance where finance_payName like concat('%',#{name},'%')
</select>

分页:

select 元素的属性
属性描述
id在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
parameterMap用于引用外部 parameterMap 的属性,目前已被废弃。请使用行内参数映射和 parameterType 属性。
resultType期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。
resultMap对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。
flushCache将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:false。
useCache将其设置为 true 后,将会导致本条语句的结果被二级缓存缓存起来,默认值:对 select 元素为 true。
timeout这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
fetchSize这是一个给驱动的建议值,尝试让驱动程序每次批量返回的结果行数等于这个设置值。 默认值为未设置(unset)(依赖驱动)。
statementType可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
resultSetTypeFORWARD_ONLY,SCROLL_SENSITIVE, SCROLL_INSENSITIVE 或 DEFAULT(等价于 unset) 中的一个,默认值为 unset (依赖数据库驱动)。
databaseId如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。
resultOrdered这个设置仅针对嵌套结果 select 语句:如果为 true,将会假设包含了嵌套结果集或是分组,当返回一个主结果行时,就不会产生对前面结果集的引用。 这就使得在获取嵌套结果集的时候不至于内存不够用。默认值:false
resultSets这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。

4.7、增删改

数据变更语句 insert,update 和 delete 的实现非常接近:

<insert id="insert" parameterType="com.th.pojo.Employee">
    insert employee(emp_name,emp_desc) values (#{empName},#{empDesc});
</insert>

<update id="update" parameterType="com.th.pojo.Employee">
    update employee set emp_name=#{empName},emp_desc=#{empDesc} where id=#{id}
</update>

<delete id="delete" parameterType="int">
    delete from employee where id=#{id}
</delete>
Insert, Update, Delete 元素的属性
属性描述
id在命名空间中唯一的标识符,可以被用来引用这条语句。
parameterType将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。
parameterMap用于引用外部 parameterMap 的属性,目前已被废弃。请使用行内参数映射和 parameterType 属性。
flushCache将其设置为 true 后,只要语句被调用,都会导致本地缓存和二级缓存被清空,默认值:(对 insert、update 和 delete 语句)true。
timeout这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。
statementType可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。
useGeneratedKeys(仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。
keyProperty(仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset)。如果生成列不止一个,可以用逗号分隔多个属性名称。
keyColumn(仅适用于 insert 和 update)设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。
databaseId如果配置了数据库厂商标识(databaseIdProvider),MyBatis 会加载所有不带 databaseId 或匹配当前 databaseId 的语句;如果带和不带的语句都有,则不带的会被忽略。

4.8、SQL片段

可以将某些重用的SQL语句提取出来,使用include标签去引用,实现代码的复用:

<!--将需要提取的SQL语句,放到sql标签中-->
<sql id="th"> where valid = #{id} </sql>
<!--在需要使用的地方用include标签引用-->
<select id = 'findId'>select * from user <include refid = 'th'></include>

注意:

  1. 最好基于单表来定义SQL片段,不要做太复杂的事情,否则重用率会大大降低!

4.9、mybatis中#和$的区别

1、#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如:

where id=#{id},如果传入的值是1,那么解析成sql时的值为where id=“1”
如果传入的值是abc,则解析成的sql为where id=“abc”。

2、$将传入的数据直接显示生成在sql中。

如:where id=${id},如果传入的值是1,那么解析成sql时的值为where username=1;
如果传入的值是;drop table user;,则解析成的sql为:

select * from student where id=1;drop table student;

3、#方式能够很大程序防止sql注入,$方式无法防止sql注入。

4、$方式一般用于传入数据库对象,比如表名。

5、一般能用#的就不要使用$,若不得不使用,则要做好前期校验工作,防止sql注入攻击。

6、在mybatis中,涉及到动态表名和列名时,只能使用${xxx}这样的参数形式。所以这样的参数需要我们在代码中手工进行处理来防止注入。

5、动态SQL

动态SQL与JSTL标签类似,它根据不同的条件生成不同的SQL语句。

if

<select id="findAll" parameterType="map" resultType="student">
    select * from student where 1=1
    <if test="name!=null">
        and name=#{name}
    </if>
    <if test="tid!=null">
        and tid=#{tid}
    </if>
</select>

choose(when,otherwise)

MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

<select id="findAll2" parameterType="map" resultType="student">
    select * from student
    <where>
        <choose>
            <when test="tid!=null">
                tid=#{tid}
            </when>
           <when test="name!=null">
               name=#{name}
           </when>
        </choose>
    </where>
</select>

trim(where,set)

trim

trim相当于where和set的父级,通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:

<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

prefixOverrides 属性会忽略指定符号分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。

where

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

如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。

<select id="findAll" parameterType="map" resultType="student">
    select * from student
    <where>
        <if test="name!=null">
            and name=#{name}
        </if>
        <if test="tid!=null">
            and tid=#{tid}
        </if>
    </where>
</select>
set

用于动态更新语句的类似解决方案叫做 setset 元素可以用于动态包含需要更新的列,忽略其它不更新的列。

<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="email != null">email=#{email},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>
<update id="update" parameterType="map">
    update student
    <set>
        tid=#{tid},
    </set>
    <where>
        id=#{id}
    </where>
</update>

以上例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的),比如最后一个要修改的参数为空,那么SQL语句就会多出一个逗号,而set元素会自动删除这个多余的逗号,所以尽情的去写逗号,不需要有所顾虑。

foreach

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

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  <where>
    <foreach item="item" index="index" collection="list"
        open="ID in (" separator="," close=")" nullable="true">
          #{item}
    </foreach>
  </where>
</select>

以上例子实际SQL语句为:SELECT * FROM POST P where 1=1 and ID in (#{item},#{item},#{item});

in条件实际可能会更多,但通过遍历集合,便可将所有条件全部加入到in条件中!

<select id="findAll3" parameterType="map" resultType="student">
    select * from student
    <where>
        <foreach collection="ids" item="id" open="and (" close=")" separator="or">
            id=#{id}
        </foreach>
    </where>
</select>

以上例子实际SQL语句为:select * from student where (id=#{id} or id=#{id} or id=#{id})

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符

提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

bind

<!--  bind可以创建一个变量并将其绑定到上下文 -->
<select id="selectUser" resultType="user">
  <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
  SELECT * FROM User
  WHERE name LIKE #{pattern}
</select>

总结

  • 动态SQL就是拼接SQL语句,只要保证SQL的正确性,按照SQL的语法,去进行各种组合就可以了!

  • 可以先在mysql中写出完整的SQL语句,然后再去拆分,修改成为动态SQL!

6、注解SQL

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

使用注解CRUD,是没有mapper.xml文件的,所以你需要使用全限定类名的方式去注册!

@Select("select * from teacher")
List<Teacher> findAll();

如果有参数,那么需要加上@param(),将参数传递过去!

//请注意,@param("id")中的id与#{id}中的id需要相同
@Select("select * from employee where id=#{id}")
Employee findId(@Param("id") int id);

当你的条件是对象是,则自动匹配,如下:

//你需要将对象中的字段名与#{}中的值相对应,这样才能匹配的上
@Insert("insert employee values(null,#{name},#{desc})")
int insert(Employee employee);

还有一些其他的注解,他们并不是增删改,仅作为一个补充,了解即可!

@SuppressWarnings("all") //抑制警告

另外,在企业中,常用UUID生成的随机数来作为id主键:

UUID.randomUUID().toString().replaceAll("-","");//replaceAll("-","")会移除掉中间的“-”

7、缓存

简介

后续会用到Redis缓存,Mybatis缓存了解即可

什么是缓存?

  1. 缓存是存储在内存中的临时数据

为什么使用缓存?

  1. 将用户经常查询的数据放到内存中,就不用去数据库读取数据,可以有效减少和数据库的交互次数,减少系统开销,提高系统效率,解决高并发系统的性能问题。
  • MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。

  • MyBatis 有两级缓存:一级缓存和二级缓存

  • 默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。(SqlSession级别)

  • 要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:

    <cache/>
    

    这个简单语句的效果如下:

    • 映射语句文件中的所有 select 语句的结果将会被缓存。
    • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
    • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
    • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
    • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
    • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
    • 提示 缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。

    可用的清除策略有:

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

一级缓存

测试

  1. 开启日志!
  2. 测试在一个Session中查询两次相同记录
  3. 查询日志输出
@Test
public void t4() {
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
    Map map=new HashMap();
    map.put("name","张三");
    List<Student> list = studentMapper.findAll(map);
    for (Student student : list) {
        System.out.println(student);
    }
    System.out.println("===========================================");
    List<Student> list2 = studentMapper.findAll(map);
    for (Student student : list2) {
        System.out.println(student);
    }
    sqlSession.close();
}

在这里插入图片描述

缓存失效情况

  1. 查询不同的东西

在这里插入图片描述

  1. 增删改操作,可能会改变原来的数据,所以必定会刷新缓存

  2. 使用不同的Mapper.xml也会使缓存失效

  3. 手动清理缓存

    sqlSession.clearCache();
    

二级缓存

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

步骤:

  1. 开启全局缓存

    <!--虽然全局缓存默认自动开启,但你仍需要显示的开启全局缓存,这是一种编程规范-->
    <setting name="cacheEnabled" value="true"/>
    
  2. 要在使用二级缓存的Mapper中开启

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

    如果你没有序列化,那么就会报错: Cause: java.io.NotSerializableException: com.th.pojo.Student

    你可以把它的详细参数写出来,这样即便你的实体类没有序列化,也仍旧可以运行

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

注意实体类最好序列化,序列化只需要实现Serializable接口即可

implements Serializable

小结

  1. 只要开启了二级缓存,在同一个Mapper下就有效
  2. 所有的数据都会先放在一级缓存中
  3. 只要当会话提交,或者关闭的时候,才会提交到二级缓存中

8、拓展

反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改其本身状态或行为的一种能力。

在 Java 环境中,反射机制允许程序在执行时获取某个类自身的定义信息,如属性和方法等,也可以实现动态创建类的对象、变更属性的内容或执行特定的方法的功能。从而使 Java 具有动态语言的特性,增强了程序的灵活性和可移植性。

Mybatis的反射是一个特别强大的模块。比如:

在mybaits中配置 useGeneratedKeys=“true” ,主键是自动生成,那么在==appVersionDao.insert(appVersion);==这一步后,Mybatis会自动生成主键并设置到appVersion里面,所以在同一个事务中,可以直接通过appVersion.getId获取到主键,而不需要额外查询一次!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值