Web基础之Mybatis

Web基础之Mybatis

  对比JdbcTempalte,mybatis才能称得上是框架,JdbcTempalte顶多算是工具类,同时,对比Hibernate,Mybatis又有更多的灵活性,算是一种折中方案。html

特色:java

  1. 支持自定义SQL、存储过程、及高级映射
  2. 实现自动对SQL的参数设置
  3. 实现自动对结果集进行解析和封装
  4. 经过XML或者注解进行配置和映射,大大减小代码量
  5. 数据源的链接信息经过配置文件进行配置

mybatis总体结构:mysql

mybatis

主配置文件
web


mybatis-config.xml

依赖sql

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

主配置文件数据库

<?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>
  <!-- 环境:说明能够配置多个,default:指定生效的环境 -->
  <environments default="development">
    <!-- id:环境的惟一标识 -->
    <environment id="development">
      <!-- 事务管理器,type:类型 -->
      <transactionManager type="JDBC" />
      <!-- 数据源:type-池类型的数据源 -->
      <dataSource type="POOLED">
        <property name="driver" value="com.mysql.jdbc.Driver" />
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis" />
        <property name="username" value="root" />
        <property name="password" value="root" />
      </dataSource>
    </environment>
  </environments>
  <!-- 映射文件 -->
  <mappers>
     <mapper resource="UserMapper.xml"/>
  </mappers>
</configuration>

映射文件:
apache


UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace(命名空间):映射文件的惟一标识 -->
<mapper namespace="UserMapper">

  <!-- 查询的statement,id:在同一个命名空间下的惟一标识,resultType:sql语句的结果集封装类型,这里须要全名 -->
  <select id="queryUserById" resultType="cn.bilibili.mybatis.pojo.User">
    select * from tb_user where id = #{id}
  </select>
  
</mapper>

使用slf4j12记录日志:
数组


log4j.properties

依赖缓存

<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.6.4</version>
</dependency>

配置文件session

log4j.rootLogger=DEBUG,A1
log4j.logger.org.apache=DEBUG
log4j.appender.A1=org.apache.log4j.ConsoleAppender
log4j.appender.A1.Target=System.err
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss,SSS} [%t] [%c]-[%p] %m%n

测试方法:


@Test

SqlSession sqlSession = null;
try {
    // 指定mybatis的全局配置文件
    String resource = "mybatis-config.xml";
    // 读取mybatis-config.xml配置文件
    InputStream inputStream = Resources.getResourceAsStream(resource);
    // 构建sqlSessionFactory
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    // 获取sqlSession会话
    sqlSession = sqlSessionFactory.openSession();
    // 执行查询操做,获取结果集。参数:1-命名空间(namespace)+“.”+statementId,2-sql的占位符参数
    User user = sqlSession.selectOne("UserMapper.queryUserById", 1L);
    System.out.println(user);
} finally {
    // 关闭链接
    if (sqlSession != null) {
        sqlSession.close();
    }
}

  大体就是经过SqlSessionFactoryBuilder类得到sql会话工厂,经过sqlSession执行sql语句,而要执行的语句及其映射bean都已经配置在xml里面。须要注意的是mybatis默认开启事务,因此执行增删改时须要手动提交。

Dao接口映射

  mybatis能够直接映射Dao接口,而没必要写其实现类(实际上是mybatis帮咱们实现了啦)

Dao接口:


UserMapper

public interface UserMapper {
    public User queryUserById(Long id);

    public List<User> queryUserList();

    public void insertUser(User user);

    public void updateUser(User user);

    public void deleteUserById(Long 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="UserMapper">
    <!-- id和方法名对应,而且惟一,所以不能有重载方法;返回类型和resultType对应; -->
    <select id="queryUserById"  resultType="cn.bilibili.mybatis.pojo.User">
        select * from tb_user where id = #{id}
    </select>

    ...

</mapper>

Tips:在IDEA中可使用Ctrl + Shift + T快速建立测试用例

数据库字段和Bean属性名称不一致问题

  • sql语句查询使用别名
  • 在主配置文件中开启驼峰匹配:
  • 自定义resultMap映射
<settings>
    <!-- 开启驼峰匹配 -->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

mybatis主配置

配置文档的顶层结构以下:

mybatis顶层结构

具体能够参考官方文档
这里介绍几个经常使用的属性。

properties

属性:properties,能够定义变量,而后使用${变量名}来取得其值,如:

<properties resource = "jdbc.properties" />

jdbc.properties内容:

driverClass = com.mysql.jdbc.Driver
url = jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf8
username = root
password = 1234

而后即可以经过${driverClass}来配置驱动了。

settings

包含<setting>标签,有namevalue属性

mapUnderscoreToCamelCase

驼峰匹配(默认关闭)

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

lazyLoadingEnabled

延迟加载(默认关闭)

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

cacheEnabled

二级缓存(默认开启)

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

autoMappingBehavior

自动映射规则:

name属性 描述 value属性 默认value
autoMappingBehavior 指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(不管是否嵌套)。 NONE, PARTIAL, FULL PARTIAL

什么沙雕排版😅

typeAliases

  类型别名是为 Java 类型设置一个短的名字。 它只和 XML 配置有关,存在的意义仅在于用来减小类彻底限定名的冗余。

<typeAliases>
    <!-- 第一种 -->
    <typeAlias alias="User" type="com.bilibili.pojo.User"/>
    <!-- 第二种 -->
    <package name="com.bilibili.pojo"/>
</typeAliases>

  第一种是直接配置一个类的别名,第二种是配置扫描一个包下的全部类
或者注解方式配置别名:

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

以及数据类型的别名(不区分大小写,基本数据类型特殊命名):


数据类型别名

别名 映射的类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

environments

Mybatis能够配置多个环境,可是一个SQLSessionFactory只对应一个环境

<!-- 能够配置多个环境,并设置默认环境 -->
<environments default="development">
    <!-- 配置环境,id:环境的惟一标识 -->
    <environment id="development">
        <!-- 事务管理器,type:使用jdbc的事务管理器 -->
        <transactionManager type="JDBC">
            <property name="..." value="..."/>
        </transactionManager>
        <!-- 数据源,type:池类型的数据源 -->
        <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>

能够经过build方法的重载指定环境:

SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environmentID);
//reader为主配置文件流,environment为environmentID,properties为读取的变量文件,三个参数的任一改变都能改变环境
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environmentID, properties);

若是忽略了环境参数,那么默认环境将会被加载,以下所示:

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

mappers

mapper的做用是告诉mybatis去哪里照执行的SQL语句,能够经过下面四种方式:

相对路径的xml文件引用(resources目录):

<!-- 使用相对于类路径的资源引用,相对的是resources目录 -->
<mappers>
    <mapper resource="UserMapper.xml"/>
</mappers>

绝对路径的xml文件引用(不推荐):

<!-- 使用彻底限定资源定位符(URL) -->
<mappers>
    <mapper url="file:///D:/UserMapper.xml"/>
</mappers>

经过接口路径

<!-- 使用映射器接口实现类的彻底限定类名 -->
<mappers>
    <mapper class="com.bilibili.mapper.UserMapper"/>
</mappers>

此方式条件:

  • 映射文件和mapper接口在同一个目录下(或者resources下的同目录)
  • 文件名一致
  • 映射文件的namespace必须和mapper接口的全路径保持一致

经过接口所在包的路径

<!-- 将包内的映射器接口实现所有注册为映射器 -->
<mappers>
    <package name="com.bilibili.mapper"/>
</mappers>

注意事项

  使用动态代理的方式实现接口映射时,mapper.xml文件的命名空间必须是接口的全限定名,而且不能使用typeAliases别名,由于别名是针对Java Bean

  前两种为直接指定xml文件位置,所以对xml路径没有什么要求。
  后两种为指定接口而后寻找xml文件,所以xml须要在和接口相同的路径(相对resources),而且!idea中resources目录不能使用.(点)来创建多级目录,须要使用/或者\来创建多级目录!!!

mapper xml 映射文件

映射文件的结构:

  • cache – 对给定命名空间的缓存配置。
  • cache-ref – 对其余命名空间缓存配置的引用。
  • resultMap – 是最复杂也是最强大的元素,用来描述如何从数据库结果集中来加载对象。
  • parameterMap – 已被废弃!老式风格的参数映射。更好的办法是使用内联参数,此元素可能在未来被移除。文档中不会介绍此元素。
  • sql – 可被其余语句引用的可重用语句块。
  • insert – 映射插入语句
  • update – 映射更新语句
  • delete – 映射删除语句
  • select – 映射查询语句

select

示例:

<select id="queryUserById" resultType="User">
    SELECT * FROM user WHERE ID = #{id}
</select>
  • id:在命名空间中惟一的标识符,能够被用来引用这条语句。
  • resultType:返回类型
  • parameterType:参数类型,能够省略
  • resultMap:结果映射,mybatis最强大的特性,不能够和resultType同时使用

insert

示例:

<insert id = "addUser" useGeneratedKeys = "true" keyColumn = "id" keyProperty = "id" parameterType = "User">
    INSERT INTO user (
        id, 
        username, 
        password
    ) VALUES (
        NULL, 
        #{userName}, 
        #{password}
    )
</insert>
  • id:惟一标识符
  • useGeneratedKeys:开启主键自增回显,将自增加的主键值回显到形参中(即封装到User对象中)[可选]
  • keyColumn:数据库中主键的字段名称 [可选]
  • keyProperty:pojo中主键对应的属性 [可选]
  • parameterType:参数类型 [可选]

update & delete

示例:

<update id = "updateUserById" parameterType = "User">
    UPDATE 
        user 
    SET 
        username = #{username},
        password = #{password}
    WHERE
        id = #{id}
</update>

<delete id = "deleteUserById" parameterType = "long">
    DELETE FROM user WHERE id = #{id}
</delete>

parameterType属性

CRUD都有个parameterType标签,用来指定接受的参数类型。

接收参数有两种方式:

  1. #{ }预编译,相似占位符
  2. ${ }非预编译,直接sql拼接,不能防止SQL注入,通常用来接收表名等属性

参数类型有三种:

  1. 基本数据类型
  2. HashMap(使用方式和pojo相似)
  3. Pojo自定义包装类

基本数据类型

当sql方法的参数是多个时:

例如queryUserByUserNameAndPassword(String username, String password)这种两个参数时,能够这样接收参数:

<select id="queryUserByUserNameAndPassword" resultType="User">
    SELECT * FROM user WHERE username = #{0}/#{arg0}/#{param1} AND password = #{1}/#{arg1}/#{param2}
</select>

不能见名知意的变量不是好方法,因此咱们的解决方案是添加@Param注解:

queryUserByUserNameAndPassword(@Param(username) String username, @Param(password)String password)
<select id="queryUserByUserNameAndPassword" resultType="User">
    SELECT * FROM user WHERE username = #{username} AND password = #{password}
</select>

这样就能够见名知意了。

HashMap参数

示例:

User loginMap(Map<String,String> map); 
/****************************************/
Map<String, String> map = new HashMap<>();
map.put("userName","zhangsan");
map.put("password","123456");
User user = userMapper.loginMap(map);

xml中和注解用法相似:

<select id="loginMap" resultType="User">
    SELECT * FROM user WHERE username = #{userName} AND password = #{password}
</select>

Pojo

mapper.xml用法不变,xml是经过Getter方法来获取值的。

${}的用法

一个参数时,默认状况下使用${value}接收数据。可是这样不能见名知意。
一样使用@Param()注解。

${ }#{ }

  • #{ }
    • 预编译
    • 编译成占位符
    • 能够防止sql注入
    • 自动判断数据类型(参数时字符串时会自动加字符串)
    • 一个参数时,可使用任意参数名称进行接收(即#{xxx})
  • ${ }SQL拼接(不能防止SQL注入)
    • 非预编译
    • sql的直接拼接
    • 不能防止sql注入
    • 须要判断数据类型,若是是字符串,须要手动添加引号。
    • 一个参数时,参数名称必须是value,才能接收参数。

  $还有一个问题是,在主配置文件中,使用${driver}获取资源文件的驱动或者url等数据,若是用户的password属性和资源文件中的password属性同名时,此时会读取资源文件中的password而不会使用传入的参数password,解决方法是在资源文件中的属性都加入前缀:

jdbc.driverClass = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://127.0.0.1:3306/mybatis?useUnicode=true&characterEncoding=utf8
jdbc.username = root
jdbc.password = 1234

固然,主配置文件里面的引用也要修改。

ResultMap

resultMap是mybatis中最强大的特性,能够很方便的解决下面两个问题:

  • Pojo属性名和表结构字段名不一致(有时候不仅是驼峰格式)
  • 高级查询(主要是这个)

简单映射示例:

在映射文件中配置自定义ResultMap:

<resultMap id="userResultMap" type="User" autoMapping="true">
    <!--配置主键映射关系,配置主键能够增长查询效率-->
    <id column="id" property="id"></id>
    <!--配置普通字段的映射关系-->
    <result column="user_name" property="userName"></result>
</resultMap>

autoMapping属性:

  • 为true时:resultMap中的没有配置的字段会自动对应。若是不配置,则默认为true。
  • 为false时:只针对resultMap中已经配置的字段做映射。

在查询语句中使用自定义映射:

<!-- resultMap属性:引用自定义结果集做为数据的封装方式 -->
<select id="queryUserById" resultMap="userResultMap">
    select * from tb_user where id = #{id}
</select>

高级查询

一对一映射

  当订单(Order)对象内有用户(User)属性时,以前的状况咱们是不能一次查询出来的,可是有了映射即可以很方便的查询:

<!-- id:惟一标识,type:返回类型,autoMapping:自动映射 -->
<resultMap id="orderResultMapper" type="Order" autoMapping="true">
<!-- 主键映射 -->
    <id column="id" property="id"/>
    <!-- 通常属性映射 -->
    <result column="order_number" property="orderNumber"/>
    <result column="user_id" property="userId"/>
    <!-- 一对一映射,property:属性,javaType:属性类型 -->
    <association property="user" javaType="User" autoMapping="true">
        <!-- 主键映射,写法和resultMap同样 -->
        <id column="user_id" property="id"/>
    </association>
</resultMap>

<select id="queryOrderByOrderNumber" resultMap="orderResultMapper">
    SELECT *
    FROM tb_order o,
         tb_user u
    WHERE o.user_id = u.id
        AND o.order_number = #{orderNumber}
</select>

一对多映射

  当一个订单内有多个信息时,即Order类中持有List<OrderDetail>,即可以进行一对多映射。
  这里的订单能够理解为多个商品一次下单,这一订单中有多个OrderDetail,每一个OrderDetail对应一个商品

<resultMap id="orderResultMapper2" type="Order" autoMapping="true">
    <!-- 主键的字段使用SQL语句的中的别名 -->
    <id column="oid" property="id"/>

    <association property="user" javaType="User" autoMapping="true">
        <id column="uid" property="id"/>
    </association>
    <!-- 一对多映射,property:类中的属性,javaType:该属性对应的Java类型,ofType:该属性存储的类型,也就是泛型 -->
    <collection property="orderDetailList" javaType="list" ofType="OrderDetail" autoMapping="true">
        <id column="did" property="id"/>
    </collection>
</resultMap>

<select id="queryOrderAndUserAndOrderDetailsByOrderNumber" resultMap="orderResultMapper2">
    <!-- 当表数量较多时,须要指定不一样表主键的别名来区分 -->
    SELECT *, o.id oid, detail.id did, u.id uid
    FROM tb_order o,
         tb_orderdetail detail,
         tb_user u
    where o.order_number = #{orderNumber}
        AND o.user_id = u.id
        AND detail.order_id = o.id;
</select>

注意,当表数量较多时,须要指定不一样表主键的别名来区分。

多对多映射

每一个OrderDetail对应一个商品,这时即可以添加映射:

<resultMap id="orderResultMapper3" type="Order" autoMapping="true">
    <id column="oid" property="id"/>

    <association property="user" javaType="User" autoMapping="true">
        <id column="uid" property="id"/>
    </association>

    <collection property="orderDetailList" javaType="list" ofType="OrderDetail" autoMapping="true">
        <id column="did" property="id"/>
        <!-- 添加一对一映射 -->
        <association property="item" javaType="Item" autoMapping="true">
            <id column="iid" property="id"/>
        </association>
    </collection>

</resultMap>

<select id="queryOrderAndUserAndOrderDetailAndItemByOrderNumber" resultMap="orderResultMapper3">
    SELECT *, o.id oid, detail.id did, u.id uid, item.id iid
    FROM tb_order o,
         tb_user u,
         tb_orderdetail detail,
         tb_item item
    where o.order_number = #{orderNumber}
        and o.user_id = u.id
        and detail.order_id = o.id
        and detail.item_id = item.id
</select>

继承

从上面的代码能够看出这样映射虽然很方便,可是代码存在冗余的状况:

冗余

  图中的代码咱们已经在其余映射中配置过了,当后面的映射须要这一段时,咱们即可以使用继承。
  修改后的第三个映射的resultMap为:

<!-- 添加extends属性,即可以映射重用 -->
<resultMap id="orderResultMapper3" type="Order" autoMapping="true" extends="orderResultMapper">

    <collection property="orderDetailList" javaType="list" ofType="OrderDetail" autoMapping="true">
        <id column="did" property="id"/>

        <association property="item" javaType="Item" autoMapping="true">
            <id column="iid" property="id"/>
        </association>
    </collection>

</resultMap>

延迟加载

  延迟加载是指当咱们须要哪部分数据时,而后再去查。
  上面的几个映射,每次查询时会一股脑将数据所有查询出来,即便不须要的数据也会查询(由于只有一条语句)。当咱们查询订单时,须要用户信息的时候再去查,所以SQL语句须要拆成两条。

主配置文件中开启延迟加载:

<settings>
    <!-- 开启延迟加载(默认关闭) -->
    <setting name="lazyLoadingEnabled" value="true" />
    <!-- 关闭使用任意属性,就加载延迟对象的功能。(在3.4.1及以前的版本默认值为 true,3.4.1以后的版本不须要配置此项) -->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

延迟加载示例:

<resultMap id="orderLazyUser" type="Order" autoMapping="true">
    <id column="id" property="id"/>
    <!-- property:属性,select:延迟加载对象依赖的SQL语句,column:传进去的参数 -->
    <association property="user" select="queryUserById" column="user_id" autoMapping="true">
        <id column="id" property="id"/>
    </association>
</resultMap>

<select id="queryOrderLazyUser" resultMap="orderLazyUser">
    SELECT * FROM tb_order where order_number = #{orderNumber}
</select>

<select id="queryUserById" resultType="User">
    SELECT * FROM tb_user WHERE id = #{id}
</select>

若是报错须要添加cglib依赖(3.3以上不须要添加此依赖):

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.1</version>
</dependency>

SQL 片断

对于使用很频繁的SQL语句,能够单独抽离出来进行复用。

在同一个mapper.xml中:

<sql id = "testSql">
    id,
    username,
    password,
    ...
</sql>

<select id = "queryAllUser" resultType = "User">
    SELECT <include refid = "testSql"></include> FROM user
</select>

可是这样只能在一个mapper文件中使用,所以咱们能够把常用的写入在一个mapper文件中:

CommonSQL.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="CommonSQL">
    <sql id="testSql">
        id,
        user_name,
        password,
        ...
    </sql>
</mapper>

在主配置中引入:

<mappers>
    <mapper resource = "CommonSQL" />
</mappers>

而后就能够在mapper中使用:

<select id = "queryAllUser" resultType = "User">
    SELECT <include refid = "CommonSQL.testSql"></include> FROM user
</select>

refid = "namespace.sqlID"变一下便可。

XML 特殊字符

感受这个好麻烦啊,由于这些字符在SQL中出现的频率过高了

符号 转义
< &lt;
> &gt;
& &amp;
' &apos;
" &quot;

或者使用CDATA:<![CDATA[]]>

真是反人类。。。

动态SQL

  mybatis中也能够动态拼接SQL语句(可是在xml中写SQL太难受了),支持ognl表达式(Struts不就是由于这东西才被黑客利用爆出漏洞死掉的么,还用。。。

if

示例:

<select id="queryMaleUserByUserNameOrId" resultType="User" >
    select * from tb_user where sex = 1
    <!-- if标签,条件判断,test属性编写ognl表达式  -->
    <if test="userName != null and userName.trim() != ''" >
        and user_name like #{userName}
    </if>
    <if test="id != null and id.trim() != '' " >
        and id like #{id}
    </if>
</select>

上面的SQL能够经过用户名或者ID来查询。

choose, when, otherwise

  至关于switch, case, default,只不过自动break,一旦有一个when成立,后续的when都再也不执行。
示例:

<select id="queryMaleUserByIdOrUserName" resultType="User">
    select * from tb_user where sex = 1 
    <choose>
        <when test="id != null and id.trim()!='' " >
            and id like #{id}
        </when>
        <when test="userName != null and userName.trim()!=''" >
            and user_name like #{userName}
        </when>
        <otherwise>
            and active = 1 
        </otherwise>
    </choose>
</select>

  上面的SQL意思是若是提供了id就用id查询,没提供id就用用户名查询,都没有的话则查询全部的激活用户。

where, set, trim

第一个查询若是把性别也改成动态的:

<select id="queryUserByUserNameOrId" resultType="User" >
    select * from tb_user where 
    <if test = "sex != null and id.trim() != '' " >
        sex = #{sex}
    </if>
    <if test = "userName != null and userName.trim() != '' " >
        and user_name like #{userName}
    </if>
    <if test = "id != null and id.trim() != '' " >
        and id like #{id}
    </if>
</select>

若是sex参数为空的话这条SQL语句就变成了这样:

select * from tb_user where 
and user_name like #{userName}
and id like #{id}

有点逗比是否是,此时<where>标签的做用就体现出来了:

<select id="queryUserByUserNameOrId" resultType="User" >
    select * from tb_user
    <where>
        <if test = "sex != null and id.trim() != '' " >
            sex = #{sex}
        </if>
        <if test = "userName != null and userName.trim() != '' ">
            and user_name like #{userName}
        </if>
        <if test = "id != null and id.trim() != '' " >
            and id like #{id}
        </if>
    </where>
</select>

  where 元素只会在至少有一个子元素的条件返回 SQL 子句的状况下才去插入“WHERE”子句。并且,若语句的开头为“AND”或“OR”,where 元素也会将它们去除。

  若是 where 元素没有按正常套路出牌,咱们能够经过自定义 trim 元素来定制 where 元素的功能。好比,和 where 元素等价的自定义 trim 元素为:

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

<set>标签和where相似:

<update id = "changePasswordOrUserName">
    UPDATE user 
    <set>
        <if test = "password != null">
            password = #{password},
        </if>
        <if test = "username != null">
            username = #{username}
        </if>
    </set>
    WHERE id = #{id}
</update>

  set 元素会动态前置 SET 关键字,同时也会删掉无关的逗号,由于用了条件语句以后极可能就会在生成的 SQL 语句的后面留下这些逗号。

上面的句子就至关于:

<trim prefix = "SET" suffixOverrides = ",">
    ...
</trim>

foreach

动态 SQL 的另一个经常使用的操做需求是对一个集合进行遍历,一般是在构建 IN 条件语句的时候。好比:

<select id="selectUserIn" resultType="User">
  SELECT *
  FROM user
  WHERE id in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

能够迭代Collection、数组、Map,当参数为Map时,index为键,item为值。

缓存

一级缓存
  缓存是sqlSession级别,默认开启(没法关闭?),可使用session.clearCache()方法清除缓存。对于同一条数据再次查询会查询缓存里的数据。须要注意的是增、删、改语句都会清除缓存,即便是不一样数据。

二级缓存
  二级缓存须要Pojo对象实现Serializable接口,能够实现不一样sqlSession间公用缓存。当第一个sqlSession查询一条数据后调用sqlSession.close()方法会将数据添加到二级缓存,第二个sqlSession再次查询同一数据时会使用缓存。(数据增删改一样会状况二级缓存)

开启二级缓存:

<settings>
    <!-- 开启二级缓存,默认是开启的 -->
    <setting name="cacheEnabled" value="true"/>
</settings>

注解方式使用Mybatis

注解须要在主配置文件中添加包扫描。

@Param

给参数添加别名(估计是由于反射不能获取接口中声明的局部变量名称
示例:

User queryUserById(@Param("id") Integer id);

@Select、@Delete、@Update、@Insert

@Select("select * from tb_user where id = #{id}")
User queryUserById(@Param("id") Integer id);

@Delete("DELETE FROM tb_user WHERE id = #{id}")
int deleteUserById(@Param("id") Integer id);

@Update("UPDATE tb_user SET name = #{name} WHERE id = #{id}")
int updateNameById(@Param("name") String name, @Param("id") Integer id);

/*
@Options:参数配置
    useGeneratedKeys:主键回写(默认false)
    keyColumn:主键字段名
    keyProperty:主键属性(默认id)
*/
@Insert("insert into tb_user(user_name,password,name) values (#{userName}, #{password}, #{name}) ")
@Options(useGeneratedKeys = true,keyColumn = "id")
int addUser(User user);

@Results注解别名映射

@Select("select id uid,user_name,password pwd from tb_user where id=#{id}")
/*
Results:定义结果集,内部是一个Result注解的数组
    Result:结果集映射关系
        column:列名
        property:属性名
*/
@Results({
    @Result(column = "uid",property = "id"),
    @Result(column = "user_name",property = "userName"),
    @Result(column = "pwd",property = "password")
})
public User findUserByIdResultMap(@Param("id") Long id);

注解高级查询

一对一映射

@Select("select * from tb_order where order_number = #{orderNumber}")
@Results({
        /*
        一对一映射调用其余接口的方法
        column:传入的参数
        property:返回对应的属性
        one表明一对一,值为@One注解
            @One:一对一注解
                select:引用的查询方法
        */
        @Result(column = "user_id",property = "user", one = @One(select = "com.bilibili.mybatis.mapper.UserMapper.queryUserById"))
})
Order queryOrderAndUserByOrderNumber(@Param("orderNumber") String orderNumber);

UserMapper接口:

//被调用的方法
public interface UserMapper {
    @Select("select * from tb_user where id = #{id}")
    User queryUserById(@Param("id") Integer id);
}

一对多映射

@Select("select * from tb_order where order_number = #{orderNumber}")
@Results({
        @Result(column = "id", property = "id"),
        //一对一映射
        @Result(column = "user_id",property = "user",one = @One(select = "com.bilibili.mybatis.mapper.UserMapper.queryUserById")),
        /*
        一对多映射
        many表明一对多,值为@Many注解
            @Many:一对多注解
                select:引用的方法
        */
        @Result(column = "id", property = "orderDetailList", many = @Many(select = "com.bilibili.mybatis.mapper.OrderDetailMapper.queryOrderDetailsByOrderId"
        ))
})
Order queryOrderAndUserAndOrderDetailsByOrderNumber(@Param("orderNumber") String orderNumber);

OrderDetailMapper接口:

//引用的方法
public interface OrderDetailMapper {
    @Select("select * from tb_orderdetail where order_id = #{oid}")
    @Results({
            @Result(column = "id",property = "id"),
    })
    List<OrderDetail> queryOrderDetailsByOrderId(@Param("oid") Integer oid);
}

多对多映射

多对多只须要在被引用的一对多方法里添加一对一便可:

public interface OrderDetailMapper {
    @Select("select * from tb_orderdetail where id = #{id}")
    OrderDetail queryOrderDetailById(@Param("id") Integer id);

    @Select("select * from tb_orderdetail where order_id = #{oid}")
    @Results({
            @Result(column = "id",property = "id"),
            //添加一对一
            @Result(column = "item_id", property = "item", one = @One(select = "com.bilibili.mybatis.mapper.ItemMapper.queryItemById"))
    })
    List<OrderDetail> queryOrderDetailsByOrderId(@Param("oid") Integer oid);
}

注解延迟加载

在@One注解里添加fetchType属性:

@Select("select * from tb_order where order_number = #{orderNumber}")
@Results({@Result(column = "user_id", property = "user", one = @One(
        select = "com.bilibili.mybatis.mapper.UserMapper.queryUserById",
        //添加延迟加载属性
        fetchType = FetchType.LAZY
))
})
Order queryOrderAndUserByOrderNumber(@Param("orderNumber") String orderNumber);

fetchType属性会覆盖全局属性,可选值有:

  • FetchType.LAZY:延迟加载
  • FetchType.EAGER:当即加载
  • FetchType.DEFAULT:默认属性(即全局属性)

注解动态SQL

注解方式使用动态SQL的话须要一个类来专门构建SQL语句:

//用来构建SQL语句的类
public class UserSqlBuilder {
    //想要在匿名内部类中访问须要将变量声明为为final
    public String queryUserByConditions(final User user) {
        //第一种方式,直接SQL拼接
        StringBuilder sqlSb = new StringBuilder("SELECT * FROM tb_user WHERE 1=1 ");
        if (user.getUserName() != null && user.getUserName().trim().length() > 0) {
            sqlSb.append(" AND user_name like #{userName} ");
        }
        if (user.getSex() != null) {
            sqlSb.append(" AND sex = #{sex} ");
        }
        return sqlSb.toString();

        //第二种方式,使用mybatis提供的类
        //注意下面的SELECT、FROM等都是方法,而且是写在构造代码块里的(猛地一看还真没看明白)
        String sql = new SQL() {{
            SELECT("*");
            FROM("tb_user");
            if (user.getUserName() != null && user.getUserName().trim().length() > 0){
                WHERE("user_name like #{userName}");
            }
            if (user.getSex() != null){
                WHERE("sex = #{sex} ");
            }
        }}.toString();

        return sql;
    }
}

接口中的方法为:

public interface UserMapper {
    //只需将@Select()注解替换为@SelectProvider()便可
    //type:提供SQL语句的类,method:提供SQL的方法
    @SelectProvider(type = UserSqlBuilder.class,method = "queryUserByConditions")
    List<User> queryUserByConditions(final User user);
}

除了SelectProvider还有:

  • @InsertProvider
  • @UpdateProvider
  • @DeleteProvider
  • @SelectProvider

按需添加便可。

  若是@SelectProvider描述的抽象方法没有使用@Param给变量添加别名,而且声明了多个变量,那么提供SQL的类的方法参数须要和接口中的方法同样,也就是声明出全部的变量:

public interface UserMapper {
    @SelectProvider(type = UserSqlBuilder.class,method = "queryUserByConditions")
    List<User> queryUserByConditions(final User user, final String name);
}

提供SQL的类的方法也须要声明为接口一样的参数:

public class UserSqlBuilder {
    //提供SQL的方法也须要声明两个变量
    public String queryUserByConditions(final User user, final String name){
        ...
    }
}

  若是接口中的抽象方法使用了@Param参数,那么类中提供SQL的方法即可以用哪一个参数声明哪一个参数:

//接口
public interface UserMapper {
    @SelectProvider(type = UserSqlBuilder.class,method = "queryUserByConditions")
    List<User> queryUserByConditions(@Param("user") final User user, @Param("user") final String name);
}

提供SQL的类中的方法即可以这样写:

public class UserSqlBuilder {
    //只须要声明本身须要的类即可以了
    public String queryUserByConditions(@Param("user") final User user){
        ...
    }
}

若是Mybatis的版本在3.5.1之后,能够将这样简化:

映射的接口:

public interface UserMapper {
    @SelectProvider(UserSqlProvider.class)
    List<User> queryUserByConditions(final User user);
}

提供SQL的类:

//提供类继承一个超类:ProviderMethodResolver
class UserSqlProvider implements ProviderMethodResolver {
    //这里的方法名须要和接口中的方法名同样
    public static String queryUserByConditions(final User user){
        ...
    }
}

仍是直接看官方的文档比较好🤣:Mybatis 官方中文文档

相关文章
相关标签/搜索