Java小记-MyBatis(超级无敌认真好用,万字收藏篇!!!!)

MyBatis

1 MyBatis简介

MyBatis是一款半自动ORM持久层框架,而大多数ORM框架底层封装的都是JDBC,因为JDBC重复代码过多,从而产生了ORM框架。

优点:

  1. 与JDBC相比,减少了50%以上的代码量。
  2. MyBatis是最简单的持久化框架,小巧并且简单易学。
  3. MyBatis灵活,不会对应用程序或者数据库的现有设计强加任何影响,SQL写在XML里,从程序代码中彻底分离,降低耦合度,便于统一管理和优化,可重用。
  4. 提供XML标签,支持编写动态SQL语句(XML中使用if, else)。
  5. 提供映射标签,支持对象与数据库的ORM字段关系映射(在XML中配置映射关系,也可以使用注解)。

1.1 什么是ORM框架

对象关系映射(Object Relational Mapping,简称ORM,或O/RM,或O/R mapping),是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。从效果上说,它其实是创建了一个可在编程语言里使用的“虚拟对象数据库”。 ORM 框架采用元数据来描述对象与关系映射的细节,元数据一般采用XML格式,并且存放在专门的对象一映射文件中。简单理解为一种框架的格式。

只要提供了持久化类与表的映射关系,ORM框架在运行时就能参照映射文件的信息,把对象持久化到数据库中。当前ORM框架主要有五种:Hibernate(Nhibernate),iBATIS,mybatis,EclipseLink,JFinal。

1.2 为什么说MyBatis是半自动持久层框架

与 全自动ORM框架不同的是,MyBatis开发需要手动编写sql语句,所以具有较高的灵活性,可以根据需要自由的对sql语句进行操作,虽然MyBatis需要手写sql语句,但相比JDBC, ORM 框架采用元数据来描述对象与关系映射的细节,元数据一般采用XML格式,并且存放在专门的对象一映射文件中。

持久层:完成持久化工作的代码层,这里为数据库相关操作

2 搭建第一个MyBatis程序

2.1 打开idea创建maven项目

2.2 导入mybatis依赖包

        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.11</version>
        </dependency>

2.3 在resources 下创建 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="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <!--分别书写驱动类、网址、账号和密码 注意网址中&使用&amp;代替-->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/test"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--引入的资源文件-->
        <mapper resource="***.xml"/>
    </mappers>
</configuration>

2.4 创建一个bean包类

@Data
public class User {
    private Integer userId;
    private String username;
    private Integer sex;
    private Date date;
    private Integer age;
    private String address;
}

2.5 创建bean包对应mapper包的接口

public interface UserMapper {
    //查询User表中所有数据
    List<User> selectAllUser();
    //给User表增加一条数据
    void  addUser(User user);
    //根据用户id删除User表一条数据
    void  deleteUserByUserid(int userid);
    //根据用户id修改表中一条数据
    void  updateUserByUserid(User user);
}

2.6 创建mapper包接口对应的xml文件或使用注解编写SQL语句

<?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.jiazhong.mybatis01.mapper.UserMapper">
    <select id="selectAllUser" resultType="com.jiazhong.mybatis01.bean.User">
        select * from user
    </select>
    <insert id="addUser" parameterType="com.jiazhong.mybatis01.bean.User">
         insert into user values(default,#{username},#{sex},now(),#{age},#{address})
    </insert>
    <delete id="deleteUserByUserid" parameterType="com.jiazhong.mybatis01.bean.User">
        delete from user where userId=#{userId}
    </delete>
    <update id="updateUserByUserid" parameterType="com.jiazhong.mybatis01.bean.User">
        update user set username=#{username} ,sex=#{sex} where userId=#{userId}
    </update>
</mapper>

注解有 @Insert , @Update,@Delect,@Select,使用注解时,config.xml里的<mappers>需引入对应映射包

    <mappers>
        <!--引入的资源文件-->
        <package name="包名"/>
        <!--引入后,config.xml会自动扫描该报下的所有资源-->
    </mappers>

此时就不需要XxxMapper.xml文件,XxxMapper.java如下

public interface UserMapper {
    //查询User表中所有数据
    @Select("select * from user")
    List<User> selectAllUser();
    //给User表增加一条数据
    @Insert("insert into user values(default,#{username},#{sex},now(),#{age},#{address}")
    void  addUser(User user);
    //根据用户id删除User表一条数据
    @Delect( "delete from user where userId=#{userId}")
    void  deleteUserByUserid(int userid);
    //根据用户id修改表中一条数据
    @Update("update user set username=#{username} ,sex=#{sex} where userId=#{userId}")
    void  updateUserByUserid(User user);
}

2.7 测试类中测试

public class Main {
    /**
     * 查询user表中所有数据
     */
    static void testSelectAllUser() throws IOException {
        //1.加载config.xml配置文件
        InputStream in = Resources.getResourceAsStream("config.xml");
        //2.建立工厂
        SqlSessionFactoryBuilder ssfb = new SqlSessionFactoryBuilder();
        //3.造手机
        SqlSessionFactory ssf = ssfb.build(in);
        //4.建立会话(java-mysql)
        SqlSession session = ssf.openSession();
        //5.获得接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        //6.执行方法
        List<User> users = userMapper.selectAllUser();
        for (User user : users) {
            System.out.println(user);
        }
        //7.关闭会话
        session.close();
    }
    //插入测试
    static void testInsertUser() throws IOException {
        InputStream in = Resources.getResourceAsStream("config.xml");
        SqlSessionFactoryBuilder ssfb =new SqlSessionFactoryBuilder();
        SqlSessionFactory ssf = ssfb.build(in);
        SqlSession session = ssf.openSession();
        UserMapper userMapper = session.getMapper(UserMapper.class);
        User user=new User();
        user.setUsername("张三");
        user.setSex(1);
        user.setAge(18);
        user.setAddress("陕西省宝鸡市");
        userMapper.addUser(user);
        session.commit();
        session.close();
    }

    //根据id删除数据测试
    static void testDeleteUserByUserid() throws IOException {
        InputStream in = Resources.getResourceAsStream("config.xml");
        SqlSessionFactoryBuilder ssfb =new SqlSessionFactoryBuilder();
        SqlSessionFactory ssf = ssfb.build(in);
        SqlSession session = ssf.openSession();
        UserMapper userMapper = session.getMapper(UserMapper.class);
        userMapper.deleteUserByUserid(18);
        session.commit();
        session.close();
    }

    //根据id修改数据测试
    static void testUpdateUserByUserid() throws IOException {
        InputStream in = Resources.getResourceAsStream("config.xml");
        SqlSessionFactoryBuilder ssfb =new SqlSessionFactoryBuilder();
        SqlSessionFactory ssf = ssfb.build(in);
        SqlSession session = ssf.openSession();
        UserMapper userMapper = session.getMapper(UserMapper.class);
        User user=new User();
        user.setUsername("小李子");
        user.setSex(0);
        user.setUserId(8);
        userMapper.updateUserByUserid(user);
        session.commit();
        session.close();
    }
    public static void main(String[] args) throws IOException {
        testUpdateUserByUserid();
    }
}

2.8 mybatis生命周期

开始
SqlSessionFactoryBuilder-工厂
静态代码块中,只加载一次
mybatis-config.xml-配置文件
SqlSessionFactory-通信工具
SqlSession-通信
close
XxxMapper-相关业务
XxxMapper-相关业务
XxxMapper-相关业务
  • SqlSessionFactoryBuilder

    一旦创建就不再需要

    作用域:局部作用域

  • SqlSessionFactory

    主要作用是创建SqlSession

    一个sqlSessionFactory对应一个数据库

    SqlSessionFactory 是重量级对象,一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。

    因此SqlSessionFactory的最佳作用域是应用作用域。

    最简单的就是使用 单例模式 或者静态单例模式

    PS:重量级:创建和销毁耗费过多资源,轻量级相反

  • SqlSession

    数据库与java之间的通信

    SqlSession是一个轻量级对象,且线程不安全,在使用后要及时关闭

    每个业务的增删改操作都是通过SqlSession来实现

    • Execute:调度执行StatementHandler、ParmmeterHandler、ResultHandler执行相应的SQL语句。
    • StatementHandler:使用数据库中的Statement(PrepareStatement)执行操作,即底层是封装好的PrepareStatement。
    • ParammeterHandler:处理SQL参数。
    • ResultHandler:结果集ResultSet封装处理放回。
  • MyBatis体系结构

API 接口层:提供给外部使用的接口 API,开发人员通过这些本地 API 来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
数据处理层:负责具体的 SQL 查找、SQL 解析、SQL 执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。

3 相关配置文件

3.1 config.xml

MyBatis 的 config.xml 配置文件结构如下:

  • configuration(配置)
    • properties(属性)
    • settings(设置)
    • typeAliases(类型别名)
    • typeHandlers(类型处理器)
    • objectFactory(对象工厂)
    • plugins(插件)
    • environments(配置环境)
      • environment(环境变量)
      • transactionManager(事务管理器)
      • dataSource(数据源)
    • databaseIdProvider(数据库厂商标识)
    • mappers(映射器)
<?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="logImpl" value="STDOUT_LOGGING"/>
        
        <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>
    <!--配置别名-->
    <typeAliases>  
        <typeAlias type="com.jiazhong.mybatis.bean.User" alias="User03"/>
    </typeAliases>
    <!--分页插件拦截器-->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!--reasonable:分页合理化参数,默认值为false。
                  当该参数设置为 true 时,pageNum<=0 时会查询第一页,
                  pageNum>pages(超过总数时),会查询最后一页。
                  默认false 时,直接根据参数进行查询。-->
            <property name="reasonable" value="true"/>
        </plugin>
    </plugins>
    <!--配置环境-->
    <environments default="development">
        <!--配置Mysql的环境-->
        <environment id="development">
            <!--配置事务的类型-->
            <transactionManager type="JDBC"/>
            <!--分别书写驱动类、网址、账号和密码 注意网址中&使用&amp;代替-->
            <dataSource type="POOLED">
                <property name="driver" value="${driver}" />
                <property name="url" value="${url}" />
                <property name="username" value="${username}" />
                <property name="password" value="${password}" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <!--引入的资源文件,不引入无法与数据库交互-->
        <mapper resource="mapper/XxxMapper.xml"/>
        <mapper resource="mapper/XxxMapper.xml"/>
        <mapper resource="mapper/XxxMapper.xml"/>
    </mappers>
</configuration>
  • 属性(properties)

    当我们需要修改数据库名,账号,密码等,若在config.xml里修改,会修改源码,所以我们把这些参数放到属性文件(.properties)中,如下

    • Jdbc.properties
    driver=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3306/数据库名
    username=数据库账号
    password=数据库密码
    
    • 在核心配置文件中引用该文件

      properties标签必须在configuration标签下面第一个

    <configuration>    
    	<!--引入外部文件 外部文件中定义了Key-Value结构数据-->
        <properties resource="Jdbc.properties"/>
        <environments default="development">
                <!--配置Mysql的环境-->
                <environment id="development">
                    <!--配置事务的类型-->
                    <transactionManager type="JDBC"/>
                    <!--分别书写驱动类、网址、账号和密码 注意网址中&使用&amp;代替-->
                    <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>
    <configuration> 
    
  • 设置(settings)

    这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。

设置名描述有效值默认值
cacheEnabled全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。ture/falsetrue
lazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。ture/falsefalse
aggressiveLazyLoading开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。ture/false版本3.4.1 (不包含) 之前 true,之后 false
multipleResultSetsEnabled是否允许单个语句返回多结果集(需要数据库驱动支持)。ture/falsetrue
useColumnLabel使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察。ture/falsetrue
useGeneratedKeys允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。ture/falsefalse
autoMappingBehavior指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。NONE, PARTIAL, FULLPARTIAL
autoMappingUnknownColumnBehavior指定自动映射当中未知列(或未知属性类型)时的行为。 默认是不处理,只有当日志级别达到 WARN 级别或者以下,才会显示相关日志,如果处理失败会抛出 SqlSessionException 异常NONE, WARNING, FAILINGNONE
defaultExecutorType配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(PreparedStatement); BATCH 执行器不仅重用语句还会执行批量更新。SIMPLE、REUSE、BATCHSIMPLE
defaultStatementTimeout设置超时时间,它决定数据库驱动等待数据库响应的秒数。任何正整数未设置 (null)
defaultFetchSize为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。任何正整数未设置 (null)
defaultResultSetType指定语句默认的滚动策略。(新增于 3.5.2)FORWARD_ONLY / SCROLL_SENSITIVE/SCROLL_INSENSITIVE / DEFAULT(等同于未设置)未设置 (null)
safeRowBoundsEnabled是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。ture/falsefalse
safeResultHandlerEnabled是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为 false。ture/falsetrue
mapUnderscoreToCamelCase是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。ture/falsefalse
localCacheScopeMyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。SESSION/STATEMENTSESSION
jdbcTypeForNull当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。 某些数据库驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。JdbcType 常量,常用值:NULL、VARCHAR 或 OTHEROTHER
lazyLoadTriggerMethods指定对象的哪些方法触发一次延迟加载。用逗号分隔的方法列表。equals,clone,hashCode,toString
defaultScriptingLanguage指定动态 SQL 生成使用的默认脚本语言。一个类型别名或全限定类名。org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
defaultEnumTypeHandler指定 Enum 使用的默认 TypeHandler 。(新增于 3.4.5)一个类型别名或全限定类名。org.apache.ibatis.type.EnumTypeHandler
callSettersOnNulls指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值进行初始化时比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。ture/falsefalse
returnInstanceForEmptyRow当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集(如集合或关联)。(新增于 3.4.2)ture/falsefalse
logPrefix指定 MyBatis 增加到日志名称的前缀。任何字符串未设置
logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J / LOG4J / LOG4J2 / JDK_LOGGING /COMMONS_LOGGING /STDOUT_LOGGING /NO_LOGGING未设置
proxyFactory指定 Mybatis 创建可延迟加载对象所用到的代理工具。CGLIB/JAVASSISTJAVASSIST (MyBatis 3.3 以上)
vfsImpl指定 VFS 的实现自定义 VFS 的实现的类全限定名,以逗号分隔。未设置
useActualParamName允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1)true/falsetrue
configurationFactory指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration()的方法。(新增于 3.2.3)一个类型别名或完全限定类名。未设置
  • 类型别名(typeAliases)

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


<typeAliases>
  <typeAlias alias="Emp" type="com.jiazhong.mybatis.bean.Emp"/>
</typeAliases>

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


<typeAliases>
  <package name="com.dailyblue.java.bean"/>
</typeAliases>

注解的写法:

@Alias("Emp")
public class Emp {
    ...
}
  • 类型处理器(typeHandlers)

MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。下表描述了一些默认的类型处理器。

类型处理器Java 类型JDBC 类型
BooleanTypeHandlerjava.lang.Boolean, boolean数据库兼容的 BOOLEAN
ByteTypeHandlerjava.lang.Byte, byte数据库兼容的 NUMERIC 或 BYTE
ShortTypeHandlerjava.lang.Short, short数据库兼容的 NUMERIC 或 SMALLINT
IntegerTypeHandlerjava.lang.Integer, int数据库兼容的 NUMERIC 或 INTEGER
LongTypeHandlerjava.lang.Long, long数据库兼容的 NUMERIC 或 BIGINT
FloatTypeHandlerjava.lang.Float, float数据库兼容的 NUMERIC 或 FLOAT
DoubleTypeHandlerjava.lang.Double, double数据库兼容的 NUMERIC 或 DOUBLE
BigDecimalTypeHandlerjava.math.BigDecimal数据库兼容的 NUMERIC 或 DECIMAL
StringTypeHandlerjava.lang.StringCHAR, VARCHAR
ClobReaderTypeHandlerjava.io.Reader-
ClobTypeHandlerjava.lang.StringCLOB, LONGVARCHAR
NStringTypeHandlerjava.lang.StringNVARCHAR, NCHAR
NClobTypeHandlerjava.lang.StringNCLOB
BlobInputStreamTypeHandlerjava.io.InputStream-
ByteArrayTypeHandlerbyte[]数据库兼容的字节流类型
BlobTypeHandlerbyte[]BLOB, LONGVARBINARY
DateTypeHandlerjava.util.DateTIMESTAMP
DateOnlyTypeHandlerjava.util.DateDATE
TimeOnlyTypeHandlerjava.util.DateTIME
SqlTimestampTypeHandlerjava.sql.TimestampTIMESTAMP
SqlDateTypeHandlerjava.sql.DateDATE
SqlTimeTypeHandlerjava.sql.TimeTIME
ObjectTypeHandlerAnyOTHER 或未指定类型
EnumTypeHandlerEnumeration TypeVARCHAR 或任何兼容的字符串类型,用来存储枚举的名称(而不是索引序数值
EnumOrdinalTypeHandlerEnumeration Type任何兼容的 NUMERIC 或 DOUBLE 类型,用来存储枚举的序数值(而不是名称)。
SqlxmlTypeHandlerjava.lang.StringSQLXML
InstantTypeHandlerjava.time.InstantTIMESTAMP
LocalDateTimeTypeHandlerjava.time.LocalDateTimeTIMESTAMP
LocalDateTypeHandlerjava.time.LocalDateDATE
LocalTimeTypeHandlerjava.time.LocalTimeTIME
OffsetDateTimeTypeHandlerjava.time.OffsetDateTimeTIMESTAMP
OffsetTimeTypeHandlerjava.time.OffsetTimeTIME
ZonedDateTimeTypeHandlerjava.time.ZonedDateTimeTIMESTAMP
YearTypeHandlerjava.time.YearINTEGER
MonthTypeHandlerjava.time.MonthINTEGER
YearMonthTypeHandlerjava.time.YearMonthVARCHAR 或 LONGVARCHAR
JapaneseDateTypeHandlerjava.time.chrono.JapaneseDateDATE
  • 对象工厂(objectFactory)

每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。 如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现。比如:

// 自定义对象工厂
public class OwnObjectFactory extends DefaultObjectFactory {
  public Object create(Class type) {
    return super.create(type);
  }
  public Object create(Class type, List<Class> constructorArgTypes, List<Object> constructorArgs) {
    return super.create(type, constructorArgTypes, constructorArgs);
  }
  public void setProperties(Properties properties) {
    super.setProperties(properties);
  }
  public <T> boolean isCollection(Class<T> type) {
    return Collection.class.isAssignableFrom(type);
  }
}

config.xml 中使用自定义的对象工厂。

<objectFactory type="com.dailyblue.java.config.GuanWeiObjectFactory">
  <property name="someProperty" value="100"/>
</objectFactory>

ObjectFactory 接口很简单,它包含两个创建实例用的方法,一个是处理默认无参构造方法的,另外一个是处理带参数的构造方法的。 另外,setProperties 方法可以被用来配置 ObjectFactory,在初始化你的 ObjectFactory 实例后, objectFactory 元素体中定义的属性会被传递给 setProperties 方法

  • 插件(plugins)

MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

  • Executor:拦截内部执行器,它负责调用 StatementHandler 操作数据库,并把结果集通过

  • ResultSetHandler 进行自动映射,另外它还处理了二级缓存的操作。

  • StatementHandler:拦截 SQL 语法构建的处理,它是 MyBatis 直接和数据库执行 SQL 脚本的对象,另外它也实现了 MyBatis 的一级缓存。

  • ParameterHandler:拦截参数的处理。

  • ResultSetHandler:拦截结果集的处理。

    这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为在试图修改或重写已有方法的行为时,很可能会破坏 MyBatis 的核心模块。 这些都是更底层的类和方法,所以使用插件的时候要特别当心。

通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

public interface Interceptor {  
   // intercept 方法就是要进行拦截的时候要执行的方法。
   Object intercept(Invocation invocation) throws Throwable;      
   // plugin 方法是插件用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理,可以决定是否要进行拦截进而决定要返回一个什么样的目标对象,官方提供了示例:return Plugin. wrap(target, this);
   Object plugin(Object target);   
   // setProperties 方法是在 MyBatis 进行配置插件的时候可以配置自定义相关属性,即:接口实现对象的参数配置;
   void setProperties(Properties properties);
 
}
@Intercepts({@Signature(type = Executor. class, method = "query",
        args = {MappedStatement. class, Object. class, RowBounds. class, ResultHandler. class})})
 
public class TestInterceptor implements Interceptor {
 
   public Object intercept(Invocation invocation) throws Throwable {
 
     Object target = invocation. getTarget(); //被代理对象
 
     Method method = invocation. getMethod(); //代理方法
 
     Object[] args = invocation. getArgs(); //方法参数
 
     // do something . . . . . .  方法拦截前执行代码块
 
     Object result = invocation. proceed();
 
     // do something . . . . . . . 方法拦截后执行代码块
 
     return result;
 
   }
 
   public Object plugin(Object target) {
 
     return Plugin. wrap(target, this);
 
   }
 
}
<plugins>
  <plugin interceptor="org.mybatis.example.TestInterceptor">
    <property name="someProperty" value="100"/>
  </plugin>
</plugins>
  • 环境配置(environments)

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

    environments 元素定义了如何配置环境。

    <!--配置环境 引用那个配置环境-->
    <environments default="development">
         <!--配置Mysql的环境 这里可以配置多个 ID是当前的唯一名字-->
         <environment id="development">
             <!--配置事务的类型-->
             <transactionManager type="JDBC"></transactionManager>
             <!--配置数据源(连接池)-->
             <dataSource type="POOLED">
                 <!--配置连接数据库的四个基本信息-->
                 <property name="driver" value="${driver}" />
                 <property name="url" value="${url}" />
                 <property name="username" value="${username}" />
                 <property name="password" value="${password}" />
             </dataSource>
         </environment>
    </environments>
    
    • 注意一些关键点:

      • 默认使用的环境 ID(比如:default=“development”),从配置文件中的 environment 中寻找 id=“development” 的 envuronment。
      • 每个 environment 元素定义的环境 ID(比如:id=“development”)。
      • 事务管理器的配置(比如:type=“JDBC”)。
      • 数据源的配置(比如:type=“POOLED”)。
    • 默认环境和环境 ID 顾名思义。 环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID。

  • 事务管理器(transactionManager)

    在 MyBatis 中有两种类型的事务管理器(也就是 type=“[JDBC|MANAGED]”):

    • JDBC:这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
    • MANAGED:这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。

    注意:如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。

  • 数据源(dataSource)

    我们在实际开发中都会使用连接池,因为它可以减少我们获取连接所消耗的时间,dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。大多数 MyBatis 应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。在mybatis中连接池提供了3种方式的配置:

    • UNPOOLED:这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。 性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。

    • POOLED:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这种处理方式很流行,能使并发 Web 应用快速响应请求。

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

  • 数据库厂商标识(databaseIdProvider)

    MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 MyBatis 会加载带有匹配当前数据库 databaseId 属性和所有不带 databaseId 属性的语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。

    
    <databaseIdProvider type="DB_VENDOR">
      <property name="SQL Server" value="sqlserver"/>
      <property name="DB2" value="db2"/>
      <property name="Oracle" value="oracle" />
    </databaseIdProvider>
    
    
  • 映射器(mappers)

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

    使用相对于类路径的资源引用(XML写法):

    <mappers>
      <mapper resource="com/dailyblue/java/mybatis/mapper/EmpMapper.xml"/>
      <mapper resource="com/dailyblue/java/mybatis/mapper/DeptMapper.xml"/>
    </mappers>
    

    使用映射器接口实现类的完全限定类名(注解写法):

    <mappers>
      <mapper class="com.dailyblue.java.mapper.EmpMapper"/>
      <mapper class="com.dailyblue.java.mapper.DeptMapper"/>
    </mappers>
    

    将包内的映射器接口实现全部注册为映射器:

    <mappers>
      <package name="com.dailyblue.java.mapper"/>
    </mappers>
    

3.2 XxxMapper.xml

MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。

  • XML 映射文件只有很少的几个顶级元素:

    • cache:该命名空间的缓存配置。
    • cache-ref:引用其它命名空间的缓存配置。
    • resultMap:描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
    • parameterMap:老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。
    • sql:可被其它语句引用的可重用语句块。
    • insert:映射插入语句。
    • update:映射更新语句。
    • delete:映射删除语句。
    • select:映射查询语句。
  • select 标签
    <select id="selectEmp" resultType="com.jiazhong.mybatis.bean.Emp">
          select * from emp 
    </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这个设置仅适用于多结果集的情况。它将列出语句执行后返回的结果集并赋予每个结果集一个名称,多个名称之间以逗号分隔。

    insert、update 和 delete 标签

    <insert id="insertEmp" parameterType="com.jiazhong.mybatis.bean.Emp">
            insert into emp
            values (null, #{ename}, #{job}, #{mgr}, now(), #{sal}, #{comm}, #{deptNo})
    </insert>
    <update id="updateEmpById" parameterType="com.jiazhong.mybatis.bean.Emp">
            update emp
            set ename=#{ename},
                job=#{job},
                mgr=#{mgr},
                sal=#{sal}
            where empNo = #{empNo}
    </update>
    <delete id="deleteEmpById" parameterType="com.jiazhong.mybatis.bean.Emp">
            delete from emp
            where empno = #{empNo}
    </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 的语句;如果带和不带的语句都有,则不带的会被忽略。
  • sql 标签

    这个元素可以用来定义可重用的 SQL 代码片段,以便在其它语句中使用。 参数可以静态地(在加载的时候)确定下来,并且可以在不同的 include 元素中定义不同的参数值。

    <sql id="test"> ${obj}.empno,${obj}.ename,${obj}.job</sql>
     
    <select id="selectEmp" resultType="emp">
      select
        <include refid="test"><property name="obj" value="emp"/></include>
      from emp
    </select>
    

4 #{}和${}的区别

在实际开发中,我们一般使用#{},在MyBatis使用#{}时,最后会被解析为?,其实就是JDBC中 PreparedStatement中的?占位符 , ${}是字符串替换,是拼接方式 , 一般会用在模糊查询的情景

  • 区别:

    #{}是预编译处理,最终会被解析为PreparedStatement中的?占位符;
    ${}是字符串替换,是拼接方式。

    #{}的变量替换是在DBMS中、变量替换后,#{}对应类型的变量自动加上单引号,可很大程度防止SQL注入;
    ${}的变量替换是在DBMS外、变量替换后,${}对应类型的变量不会加上单引号,无法防止SQL注入。

5 分页查询

分页查询主要为两种

5.1 limit分页查询

  • UserMapper接口

    public interface UserMapper {
        //分页查询
        List<User> selectUser(@Param("start") Integer start,@Param("size") Integer size);
    }
    
  • UserMapper.xml

    <select id="selectUser" resultType="com.jiazhong.mybatis02.bean.User">
            select * from user limit #{start},#{size}
        </select>
    
  • 测试类

        private static SqlSession session;
        private UserMapper getUserMapper() {
            InputStream in = null;
            try {
                in = Resources.getResourceAsStream("config.xml");
            } catch (IOException e) {
                e.printStackTrace();
            }
            SqlSessionFactoryBuilder ssfb = new SqlSessionFactoryBuilder();
            SqlSessionFactory ssf = ssfb.build(in);
            session = ssf.openSession();
            UserMapper userMapper = session.getMapper(UserMapper.class);
            return userMapper;
        } 
       @Test
        public void selectUser(){
            List<User> users = getUserMapper().selectUser(3, 3);
            for (User user : users) {
                System.out.println(user);
            }
            session.close();
        }
    

5.2 多参数传递问题

MyBatis默认情况下不支持多个参数

  1. 封装

    把多个参数封装到一个类,将这个类作为参数

  2. map集合

    把多个参数放到map集合中,将map集合作为参数

  3. 注解

    给每个参数起一个名字

    List<User> selectUserByUseridAndUserName03(@Param("userId") Integer userId
                                               ,@Param("username") String username);
    

5.3 PageHelper 分页插件

  • 导入依赖

    <!--PageHelper分页查询-->
            <dependency>
                <groupId>com.github.pagehelper</groupId>
                <artifactId>pagehelper</artifactId>
                <version>5.3.2</version>
            </dependency>
    
  • 在 config.xml 中配置 PageHelper 插件

        <!--分页插件拦截器-->
        <plugins>
            <plugin interceptor="com.github.pagehelper.PageInterceptor">
                <!--reasonable:分页合理化参数,默认值为false。
                      当该参数设置为 true 时,pageNum<=0 时会查询第一页,
                      pageNum>pages(超过总数时),会查询最后一页。
                      默认false 时,直接根据参数进行查询。-->
                <property name="reasonable" value="true"/>
            </plugin>
        </plugins>
    
  • 测试全查询

        public void selectUser(){
            /**
             * 参数1:pageNum 多少页
             * 参数2:pageSize 每页显示多少条
             */
            PageHelper.startPage(3,2);
            List<User> users = getUserMapper().selectUserByPageHelper();
            //pageInfo可以显示详细信息
            PageInfo<User> pageInfo=new PageInfo<>();
            for (User user : users) {
                System.out.println(user);
            }
            System.out.println(pageInfo);
            session.close();
        }
    

PS:引用包名过长,如何解决?

config.xml起别名

   <typeAliases>
        <typeAlias type="com.jiazhong.mybatis02.bean.User" alias="User">
    </typeAliases>

7 MyBaties映射关系

顾名思义,就是数据库中的数据与Java中的实体类一一对应

7.1 属性名与列名不一致解决方案

  1. 起别名

    给查询列起别名,与属性名一致即可

  2. 手动映射

         <select id="selectUserByUserid02" resultType="com.jiazhong.mybatis.bean.User"          resultMap="map1"><!-- 调用映射-->
            select *
            from user
            where userId = ${userId}
        </select>
        <!--
            手动映射
            id:手动映射名
            type:对那个类进行映射
        -->
        <resultMap id="map1" type="User03">
            <!-- 主键映射方案-->
            <id property="userId" column="userid" javaType="java.lang.Integer" jdbcType="INTEGER"/>
            <!-- 非主键映射方案-->
            <result property="age1" column="age" jdbcType="INTEGER" javaType="java.lang.Integer"/>
        </resultMap>
    

    ResultMap 的属性列表:

    属性描述
    id当前命名空间中的一个唯一标识,用于标识一个结果映射。(通俗的将讲就是当前映射的映射名)
    type类的完全限定名, 或者一个类型别名(就是对哪个javaBean类进行映射)
    autoMapping如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。 这个属性会覆盖全局的属性 autoMappingBehavior。默认值:未设置(unset)。
    • 子标签id和result属性列表

      id用作主键映射,result用作非主键映射

      属性描述
      property对应javaBean类中的属性
      column对应数据库中的列名或是列的别名
      javaType该属性对应java的数据类型
      jdbcType对应该数据库表对应的数据类型
      typeHandler我们在前面讨论过默认的类型处理器。使用这个属性,你可以覆盖默认的类型处理器。 这个属性值是一个类型处理器实现类的全限定名,或者是类型别名。

7.2 多对一关系

以dept和emp表为例,一个部门可对应多个员工,那么员工和部门之间就是多对一的关系。

  • java中使用关联和拼接处理该问题
  1. 拼接方式处理

    • 查询员工信息

          <select id="selectEmpno" resultType="com.jiazhong.mybatis.bean.manyToOne.Emp">
              select *
              from emp
              where EMPNO = #{EMPNO}
          </select>
      
    • 查询部门信息

          <select id="selectDeptByDeptno" resultType="com.jiazhong.mybatis.bean.manyToOne.Dept">
              select *
              from dept
              where DEPTNO = #{DEPTNO}
          </select>
      
    • 用service进行拼接

          @Override
          public Emp selectEmpNO(int id) {
              //员工信息
              Emp emp = empMapper.selectEmpno(id);
              //部门信息
              Dept dept = deptMapper.selectDeptByDeptno(emp.getDEPTNO());
              //组装
              emp.setDept(dept);
              return emp;
          }
      
    • 测试

          //通过员工id查询员工信息及其对应部门
          static void selectEmpByEmpno(){
              EmpService empService=new EmpServiceImpl(empMapper,deptMapper);
              Emp emp = empService.selectEmpNO(7788);
              System.out.println(emp);
          }
      
  • 优点:有效避免SQL联查(有可能出现迪卡尔积)
  • 缺点:效率低下,多次连接数据库
  1. 关联(association)元素处理

    • 表联查及通过resultMap配置映射

      • 参数autoMapping:设置为true根据类型自动映射
         <select id="selectEmpno01" resultType="com.jiazhong.mybatis.bean.manyToOne.Emp" resultMap="map01">
              select *
              from emp
                       left join dept on emp.DEPTNO = dept.DEPTNO
              where EMPNO = #{EMPNO}
          </select>
          <!--手动映射-->
          
          <resultMap id="map01" type="com.jiazhong.mybatis.bean.manyToOne.Emp" autoMapping="true">
              <id property="EMPNO" column="EMPNO"/>
              <result property="ENAME" column="ENAME"/>
              <result property="JOB" column="JOB"/>
              <result property="MGR" column="MGR"/>
              <result property="HIREDATE" column="HIREDATE"/>
              <result property="SAL" column="SAL"/>
              <result property="COMM" column="COMM"/>
              <result property="DEPTNO" column="DEPTNO"/>
              <association property="dept" javaType="com.jiazhong.mybatis.bean.manyToOne.Dept">
                  <id property="DEPTNO" column="DEPTNO"/>
                  <result property="DNAME" column="DNAME"/>
                  <result property="LOC" column="LOC"/>
           </associaton>
          </resultMap>
      
      
    • 测试

        static void selectEmpByEmpno(){
                   Emp emp = empMapper.selectEmpno01(7788);
                   System.out.println(emp);
               }
      
  • 优点:只需连接一次数据库,运行效率高
  • 缺点:有可能出现笛卡尔积

7.3 一对多关系

与上文对应,员工和部门为多对一,则部门和员工为一对多关系

  1. 拼接方式处理

    • 查询部门信息

         <select id="selectDeptByDeptno02" resultType="com.jiazhong.mybatis.bean.oneToMany.Dept01">
              select *
              from dept
              where DEPTNO = #{DEPTNO}
          </select>
      
    • 查询员工信息

          <select id="selectEmpno02" resultType="com.jiazhong.mybatis.bean.oneToMany.Emp01">
              select *
              from emp
              where DEPTNO = #{DEPTNO}
          </select>
      
    • 用service进行拼接

       public Dept01 selectEmpNO02(int id) {
              Dept01 dept = deptMapper.selectDeptByDeptno02(id);
              Set<Emp01> emp01s = empMapper.selectEmpno02(dept.getDEPTNO());
              dept.setEmps(emp01s);
              return dept;
          }
      
    • 测试

        static void selectDeptByDeptno() {
              DeptService deptService = new DeptServiceImpl(empMapper, deptMapper);
              Dept01 dept01 = deptService.selectEmpNO02(20);
              System.out.println("部门信息:"+dept01);
              System.out.println("部门员工有:");
              Set<Emp01> emps = dept01.getEmps();
              for (Emp01 emp: emps) {
                  System.out.println(emp);
              }
          }
      
  2. 集合( collection )方式处理

    • 表联查通过resultMap配置映射

          <select id="selectDeptByDeptno03" resultType="com.jiazhong.mybatis.bean.oneToMany.Dept01" resultMap="map02">
              select * from dept left join emp e on dept.DEPTNO = e.DEPTNO where dept.DEPTNO=#{DEPTNO}
          </select>
          
          
          <resultMap id="map02" type="com.jiazhong.mybatis.bean.oneToMany.Dept01">
              <id property="DEPTNO" column="DEPTNO"/>
              <result property="LOC" column="LOC"/>
              <result property="DNAME" column="DNAME"/>
              <collection property="emps" ofType="com.jiazhong.mybatis.bean.oneToMany.Emp01">
                  <id property="EMPNO" column="EMPNO"/>
                  <result property="SAL" column="SAL"/>
                  <result property="COMM" column="COMM"/>
                  <result property="HIREDATE" column="HIREDATE"/>
                  <result property="JOB" column="JOB"/>
                  <result property="ENAME" column="ENAME"/>
                  <result property="DEPTNO" column="DEPTNO"/>
                  <result property="MGR" column="MGR"/>
              </collection>
          </resultMap>
      
    • 测试

          static void selectDeptByDeptno01(){
              Dept01 dept = deptMapper.selectDeptByDeptno03(20);
              System.out.println("部门信息:"+dept);
              System.out.println("部门员工有");
              Set<Emp01> emps = dept.getEmps();
              for (Emp01 emp: emps) {
                  System.out.println(emp);
              }
          }
      
  • PS:一对多时,collection标签,映射类型属性,是oftype而不是javatype

8 动态SQL

在以前的开发中,不同条件的拼接SQL实现起来很痛苦,例如: 拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号,而利用动态 SQL,可以彻底摆脱这种痛苦。

  • mybatis动态标签主要有
    • if
    • choose (when, otherwise)
    • trim (where, set)
    • foreach
    • bind

8.1 if标签

<select id="selectEmpByEname" resultType="com.jiazhong.bean.Emp" >
        select * from emp
                   <if test="ENAME!=null">
                       where  ENAME like #{ENAME}
                   </if>
  				   <if test="EJOB!=null">
                       and  EJOB=#{EJOB}
                   </if>
</select>

8.2 trim (where, set)标签

  • 如上面if标签例子,如果where在上面时
<select id="selectEmpByEname" resultType="com.jiazhong.bean.Emp" >
        select * from emp where
                   <if test="ENAME!=null">
                        ENAME like #{ENAME}
                   </if>
  				   <if test="EJOB!=null">
                       and  EJOB=#{EJOB}
                   </if>
</select>
  • 若if语句不执行,则sql语句如下
select * from emp where
  • 此时sql语句出错,所以引入了where标签,如下
<select id="selectEmpByEname" resultType="com.jiazhong.bean.Emp" >
        select * from emp 
                <where>
                   <if test="ENAME!=null">
                        ENAME like #{ENAME}
                   </if>
  				   <if test="EJOB!=null">
                       and  EJOB=#{EJOB}
                   </if>
                </where>
</select>
  • 此时where标签只有在if子句执行时,才会插入where语句,并且若开头为andor也会自动省略

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

    prefix:自定义元素,如下自定义为where
    prefixOverrides:忽略第一次出现元素,如下自定义为AND|OR
    suffix:尾部加入自定义元素
    suffixOverrides:尾部忽略属性

<!--
   prefix:自定义元素,如下自定义为where
   prefixOverrides:忽略第一次出现元素,如下自定义为AND|OR
   suffix:尾部加入自定义元素
   suffixOverrides:尾部忽略属性
-->
<trim prefix="WHERE" prefixOverrides="AND|OR" suffix=";" suffixOverrides="">
....
</trim>
  • 类似的在更新标签update里,我们需要修改部分列,忽略其他不更新的列则如下
    <update id="updateEmpBy">
         update emp
         <set>
             <if test="ENAME!=null">
                 ENAME=#{ENAME},
             </if>
             <if test="JOB!=null">
                 JOB=#{JOB},
             </if>
             <if test="MGR!=null">
                 MGR=#{MGR},
             </if>
         </set>
         <where>
             EMPNO=#{EMPNO}
         </where>
    </update>
  • trim标签自定义则为
<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

8.3 choose (when, otherwise) 标签

  • 有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 标签,它有点像 Java 中的 switch 语句。

        <select id="selectEmpByEname" resultType="com.jiazhong.bean.Emp">
            select * from emp
            <where>
                <choose>
                    <when test="ENAME!=null">
                        ENAME like concat('%',#{ENAME},'%')
                    </when>
                    <when test="JOB!=null">
                        and JOB=#{JOB}
                    </when>
                    <otherwise>
                        order by SAL desc
                    </otherwise>
                </choose>
            </where>
        </select>
    

8.4 foreach 标签

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

foreach标签的属性

  • item:表示集合中每一个元素进行迭代时的别名。
  • index:指定一个名字,表示在迭代过程中每次迭代到的位置。
  • open:表示该语句以什么开始(既然是 in 条件语句,所以必然以’‘(’'开始)。
  • separator:表示在每次进行迭代之间以什么符号作为分隔符(既然是 in 条件语句,所以必然以’‘,’'作为分隔符)。
  • close:表示该语句以什么结束(既然是 in 条件语句,所以必然以’‘)’'结束)。
  • collection:使用 foreach 标签时,最关键、最容易出错的是 collection 属性,该属性是必选的,但在不同情况下该属性的值是不一样的,主要有以下 3 种情况:
    • 如果传入的是单参数且参数类型是一个 List,collection 属性值为 list。
    • 如果传入的是单参数且参数类型是一个 array 数组,collection 的属性值为 array。
    • 如果传入的参数是多个,需要把它们封装成一个 Map,当然单参数也可以封装成 Map。Map 的 key 是参数名,collection 属性值是传入的 List 或 array 对象在自己封装的 Map 中的 key。
    <select id="selectEmpByJob" resultType="com.jiazhong.bean.Emp">
        select * from emp  where JOB in
        <foreach collection="list" open="(" close=")" separator="," index="i" item="job">
            #{job}
        </foreach>
    </select>

8.5 bind 标签

  • bind 元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文。
<select id="findEmpByEname" resultType="emp">
  <bind name="pattern" value="'%' + _parameter.getEname() + '%'" />
  SELECT * FROM emp WHERE ename LIKE #{pattern}
</select>

8.6 级联查询案例

代码在这

9 MyBatis的缓存

Mybatis 是一个对数据库进行操作的ORM框架,主要是封装提供灵活的增删改查sql,开发中,service层能够通过mybatis组件查询和修改数据库中表的数据;作为查询工具,mybatis有使用缓存。

  • 所谓缓存其实就是读写速度不批配,为了增强资源利用率而产生的

  • 缓存:高速读,与低速写之间的缓存,可以直接在缓存中读取,减少硬件的使用,以提高读取速度

  • MyBatis 的缓存

    mybatis 也是这样,查询数据库的数据之后,mybatis 可以把查询结果缓存到内存,下次查询如果查询语句相同,并且查询相关的表的数据没被修改过,就可以直接返回缓存中的结果,而不用去查询数据库的语句,有效节省了时间。

    MyBatis缓存分为:

    • 一级缓存: SqlSession级别的缓存,缓存的数据只在SqlSession内有效。
    • 二级缓存: mapper级别的缓存,同一个namespace公用这一个缓存,所以对SqlSession是共享的,二级缓存需要我们手动开启。

9.1 一级缓存

基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空。一级缓存默认是长开启的。

  • 一级缓存是系统自带的缓存,不需要任何额外的操作。同一个 session ,对一个数据进行多次查询,第二次就不需要连接数据库
      static void selectEmpAndDeptByEmpno(){
          Emp emp1 = empMapper.selectEmpAndDeptByEmpno(7788);
          Emp emp2 = empMapper.selectEmpAndDeptByEmpno(7788);
          System.out.println(emp1);
          System.out.println(emp2);
    }
  • 输出的日志:
==>  Preparing: select * from emp where EMPNO=?
==> Parameters: 7788(Integer)
<==    Columns: EMPNO, ENAME, JOB, MGR, HIREDATE, SAL, COMM, DEPTNO
<==        Row: 7788, SCOTT, ANALYST, 7566, 1987-04-19, 3000.0, null, 20
====>  Preparing: select * from dept where DEPTNO=?
====> Parameters: 20(Integer)
<====    Columns: DEPTNO, DNAME, LOC
<====        Row: 20, RESEARCH, DALLAS
<====      Total: 1
<==      Total: 1
Emp(EMPNO=7788, ENAME=SCOTT, JOB=ANALYST, MGR=7566, HIREDATE=1987-04-19, SAL=3000.0, COMM=null, DEPTNO=null, dept=Dept(DEPTNO=20, DNAME=RESEARCH, LOC=DALLAS, emps=null))
Emp(EMPNO=7788, ENAME=SCOTT, JOB=ANALYST, MGR=7566, HIREDATE=1987-04-19, SAL=3000.0, COMM=null, DEPTNO=null, dept=Dept(DEPTNO=20, DNAME=RESEARCH, LOC=DALLAS, emps=null))
  • 生命周期

    MyBatis产生数据库会话时,会创建SqlSession对象时,SqlSession 对象中会有一个新的 Executor 对象。Executor 对象中持有一个新的 PerpetualCache 对象。当会话结束时,SqlSession 对象及其内部的 Executor 对象还有 PerpetualCache 对象也一并释放掉 。

  • 一级缓存如何失效

    • 如果 SqlSession 调用了 close()方法,会释放掉一级缓存 PerpetualCache 对象,一级缓存将不可用。
    • 如果 SqlSession 调用了 clearCache(),会清空 PerpetualCache 对象中的数据,但是该对象仍可使用。
    • SqlSession 中执行了任何一个 update 操作(update()、delete()、insert()) ,都会清空 PerpetualCache 对象的数据,但是该对象可以继续使用。
  • MyBatis内部如何判断两次查询相同

    • 对于某个查询,根据statementId,params,rowBounds来构建一个key值,根据这个key值去缓存Cache中取出对应的key值存储的缓存结果;
    • 判断从Cache中根据特定的key值取的数据数据是否为空,即是否命中;
    • 如果命中,则直接将缓存结果返回;
    • 如果没命中:
    • 去数据库中查询数据,得到查询结果;
    • 将key和查询到的结果分别作为key,value对存储到Cache中;
    • 将查询结果返回;
    • 结束

9.2 二级缓存

二 级 缓 存 与 一 级 缓 存 其 机 制 相 同 , 默 认 也 是 采 用 PerpetualCache , HashMap 存 储 , 不 同 在 于 其 存 储 作 用 域 为Mapper,也被称之为 namespace 级别缓存,并且可自定义存储源,如 Ehcache。

  • 为什么有二级缓存?

    MyBatis 一级缓存最大的共享范围就是一个SqlSession内部,那么如果多个 SqlSession 需要共享缓存,则需要开启二级缓存。

    Spring 和 MyBatis 整合时,每次查询之后都要进行关闭 sqlsession ,关闭之后数据被清空。所以 MyBatis 和 Spring 整合之后,一级缓存是没有意义的。如果开启二级缓存,关闭 sqlsession 后,会把该 sqlsession 一级缓存中的数据添加到 mapper namespace 的二级缓存中。

  • 开启二级缓存

    默认关闭二级缓存的,因为对于增删改操作频繁的话,那么二级缓存形同虚设,每次都会被清空缓存。

    1. bean 包中的类需要序列化。

      import lombok.AllArgsConstructor;
      import lombok.Data;
      import lombok.NoArgsConstructor; 
      import java.io.Serializable; 
      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      public class Emp implements Serializable {
          ......
      }
      
    2. 在 config.xml 中设置二级缓存开启。

    <settings>
        <setting name="cacheEnabled" value="true" />
    </settings>
    
    1. 对具体的 Mapper 单独允许二级缓存的使用。

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

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

      PS: 多个 session 使用一个缓存数据时,必须等待第一个 session 释放资源,其后 session 才能使用缓存数据。

  • cache 标签的相关属性

    <!--这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。 -->
    <cache
      eviction="FIFO"
      flushInterval="60000"
      size="512"
      readOnly="true"/>
    

    可用的 eviction 清除策略有(默认为LRU):

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

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

10 MyBatis的注解

MyBatis并不只是能以XML方式实现来实现,还可以注解方式实现,MyBatis注解如下:

  • 基本注解
  • 关系注解
  • 动态SQL操作
  • 缓存注解

10.1 基本注解

  • @Select@Insert@Update@Delete 注解

    可实现基本增删改查操作

        //查询emp表所有员工
        @Select("select * from Emp")
        List<Emp> selectEmp();
        //插入一条数据
        @Insert("insert into emp value (#{EMPNO},#{ENAME},#{JOB},#{MGR} ,#{HIREDATE} ,#{SAL} ,#{COMM} ,#{DEPTNO})")
        void insertEmpById(Emp emp);
        //根据员工编号修改信息
        @Update("update emp set ENAME=#{ENAME},JOB=#{JOB},MGR=#{MGR} ,HIREDATE=#{HIREDATE} ,SAL=#{SAL} ,COMM=#{COMM} ,DEPTNO=#{DEPTNO} where EMPNO=#{EMPNO}")
        void updateEmpById(Emp emp);
        //删除用户
        @Delete("delete from emp where EMPNO=#{EMPNO}")
        void DeleteEmpById(int id);
    
  • @Param 注解

    MyBatis 默认不能传递多个参数,如果要传递多个参数,需要通过 @Param 注解来指定参数和#{}之间的关系。

        //查询emp表员工从第几行开始
        @Select("select * from Emp limit #{start},#{size} ")
        List<Emp> selectEmp(@Param("start") int start, @Param("size") int size);
    

10.2 关系注解

多表联查时,我们过去使用 ResultMap 标签来手动对结果集进行设置,在注解中也可以对结果集进行操作的方式。

  • @Results@Result 注解

        @Select("select * from Emp where EMPNO=#{EMPNO}")
        @Results(id = "map",value = {
                @Result(column = "MGR",property = "SAL")
        })
        List<Emp> selectEmp(int id);
    
    • @Result 的相关属性:
      • column:数据库的列名
      • property:需要装配的属性名
      • one:需要使用的@One注解@Result(one=@One)()))
      • many:需要使用的@Many注解@Result(many=@many)()))
  • @One@Many 注解

    • @One注解(多对一关系注解)
    public interface EmpMapper {
        // 获取员工信息,并通过@one获取到部门信息
        @Select("select * from emp where EMPNO=#{EMPNO}")
        @Results(id = "map" , value = {
                @Result(column = "EMPNO" ,property = "EMPNO" , id = true),
                @Result(column = "DEPTNO" ,property = "dept",javaType = Dept.class
                ,one=@One(select = "com.jiazhong.mybatis05.mapper.DeptMapper.selectDeptBy")
                )
        })
        Emp selectEmpAndDeptByEmpno(int id);
    }
    
    public interface DeptMapper {
        @Select("select * from dept where DEPTNO=#{DEPTNO}")
        Dept selectDeptBy(int id);
    }
    
    • @Many注解(多对一关系注解)
    public interface EmpMapper {
        //通过部门编号查询员工
        @Select("select * from emp where DEPTNO=#{DEPTNO}")
        List<Emp> selectEmpByDeptno();
    }
    
    
    
    public interface DeptMapper {
        @Select("select * from dept where DEPTNO=#{DEPTNO}")
        @Results(id = "map03" ,value = {
                @Result(column = "DEPTNO",property = "emps" ,javaType = List.class,
                many = @Many(select = "com.jiazhong.mybatis05.mapper.EmpMapper.selectEmpByDeptno"))
        })
        Dept selectDeptByNO(int id);
    }
    
    
    • 两个注解的相关属性:
      • select:指定用来多表查询的 sqlmapper。
      • fetchType:会覆盖全局的配置参数 lazyLoadingEnabled 。
  • ResultMap注解

    通过这个注解来使用其他方法中定义好的@Results。

    public interface DeptMapper {
        @Select("select * from dept where DEPTNO=#{DEPTNO}")
        @Results(id = "map03" ,value = {
                @Result(column = "DEPTNO",property = "emps" ,javaType = List.class,
                many = @Many(select = "com.jiazhong.mybatis05.mapper.EmpMapper.selectEmpByDeptno"))
        })
        Dept selectDeptByNO(int id);
        
        
        @Select("select * from dept where DEPTNO=#{DEPTNO}")
        @ResultMap("map03")
        Dept selectDeptByNO01(int id);
    }    
    
    

10.3 动态 SQL 操作

我们一般使用 <script> </script> 将 SQL 语句包裹住,就可以使用动态 SQL 了。

@Select("<script> + 
        select * from Emp +
        <where> + 
        <if test=\"ENAME!=null\"> and ENAME like concat('%',#{ENAME},'%') +
        </if>+
        </where> +
        </script>")
    List<Emp> selectEmp(Emp emp);

10.4 缓存注解

作用于mapper接口上面,是用来实现二级缓存的。


@CacheNamespace(blocking = true)
public interface EmpMapper{
    
}
  • blocking 属性默认值为 false,在该属性为 true 的情况下 Mybatis 采用的缓存装饰器为 BlockingCache。大意是说在执行同一 SQL 查询时当前线程会先去获取锁,其他执行该查询的 SQL 线程只能等待当前线程查询完成后才能继续查询而不是直接命中数据库。

10.5 MyBatis 注解 API

注解使用对象XML 等价形式描述
@CacheNamespace<cache>为给定的命名空间(比如类)配置缓存。属性:implemetation、eviction、flushInterval、size、readWrite、blocking、properties。
@PropertyN/A<property>指定参数值或占位符(placeholder)(该占位符能被 mybatis-config.xml 内的配置属性替换)。属性:name、value。(仅在 MyBatis 3.4.2 以上可用)
@CacheNamespaceRef<cacheRef>引用另外一个命名空间的缓存以供使用。注意,即使共享相同的全限定类名,在 XML 映射文件中声明的缓存仍被识别为一个独立的命名空间。属性:value、name。如果你使用了这个注解,你应设置 value 或者 name 属性的其中一个。value 属性用于指定能够表示该命名空间的 Java 类型(命名空间名就是该 Java 类型的全限定类名),name 属性(这个属性仅在 MyBatis 3.4.2 以上可用)则直接指定了命名空间的名字。
@ConstructorArgs方法<constructor>收集一组结果以传递给一个结果对象的构造方法。属性:value,它是一个 Arg 数组。
@ArgN/A<arg>
<idArg>
ConstructorArgs 集合的一部分,代表一个构造方法参数。属性:id、column、javaType、jdbcType、typeHandler、select、resultMap。id 属性和 XML 元素 <idArg> 相似,它是一个布尔值,表示该属性是否用于唯一标识和比较对象。从版本 3.5.4 开始,该注解变为可重复注解。
@TypeDiscriminator方法<discriminator>决定使用何种结果映射的一组取值(case)。属性:column、javaType、jdbcType、typeHandler、cases。cases 属性是一个 Case 的数组。
@CaseN/A<case>表示某个值的一个取值以及该取值对应的映射。属性:value、type、results。results 属性是一个 Results 的数组,因此这个注解实际上和 ResultMap 很相似,由下面的 Results 注解指定。
@Results方法<resultMap>一组结果映射,指定了对某个特定结果列,映射到某个属性或字段的方式。属性:value、id。value 属性是一个 Result 注解的数组。而 id 属性则是结果映射的名称。从版本 3.5.4 开始,该注解变为可重复注解。
@ResultN/A<result> <id>在列和属性或字段之间的单个结果映射。属性:id、column、javaType、jdbcType、typeHandler、one、many。id 属性和 XML 元素 <id> 相似,它是一个布尔值,表示该属性是否用于唯一标识和比较对象。one 属性是一个关联,和 <association> 类似,而 many 属性则是集合关联,和 <collection> 类似。这样命名是为了避免产生名称冲突。
@OneN/A<association>复杂类型的单个属性映射。属性:select,指定可加载合适类型实例的映射语句(也就是映射器方法)全限定名;fetchType,指定在该映射中覆盖全局配置参数 lazyLoadingEnabled。提示 注解 API 不支持联合映射。这是由于 Java 注解不允许产生循环引用。
@ManyN/A<collection>复杂类型的集合属性映射。属性:select,指定可加载合适类型实例集合的映射语句(也就是映射器方法)全限定名;fetchType,指定在该映射中覆盖全局配置参数 lazyLoadingEnabled。提示 注解 API 不支持联合映射。这是由于 Java 注解不允许产生循环引用。
@MapKey方法供返回值为 Map 的方法使用的注解。它使用对象的某个属性作为 key,将对象 List 转化为 Map。属性:value,指定作为 Map 的 key 值的对象属性名。
@Options方法映射语句的属性该注解允许你指定大部分开关和配置选项,它们通常在映射语句上作为属性出现。与在注解上提供大量的属性相比,Options 注解提供了一致、清晰的方式来指定选项。属性useCache=true、flushCache=FlushCachePolicy.DEFAULT、resultSetType=DEFAULT,statementType=PREPARED、fetchSize=-1、timeout=-1、useGeneratedKeys=false、keyProperty=“”、keyColumn=“”、resultSets=“”。注意,Java 注解无法指定 null 值。因此,一旦你使用了 Options 注解,你的语句就会被上述属性的默认值所影响。要注意避免默认值带来的非预期行为。
注意:keyColumn 属性只在某些数据库中有效(如 Oracle、PostgreSQL 等)。要了解更多关于 keyColumn 和 keyProperty 可选值信息,请查看“insert, update 和 delete”一节。
@Insert
@Update
@Delete
@Select
方法<insert>
<update>
<delete>
<select>
每个注解分别代表将会被执行的 SQL 语句。它们用字符串数组(或单个字符串)作为参数。如果传递的是字符串数组,字符串数组会被连接成单个完整的字符串,每个字符串之间加入一个空格。这有效地避免了用 Java 代码构建 SQL 语句时产生的“丢失空格”问题。当然,你也可以提前手动连接好字符串。属性:value,指定用来组成单个 SQL 语句的字符串数组。
@InsertProvider
@UpdateProvider
@DeleteProvider
@SelectProvider
方法<insert>
<update>
<delete>
<select>
允许构建动态 SQL。这些备选的 SQL 注解允许你指定返回 SQL 语句的类和方法,以供运行时执行。(从 MyBatis 3.4.6 开始,可以使用 CharSequence 代替 String 来作为返回类型)。当执行映射语句时,MyBatis 会实例化注解指定的类,并调用注解指定的方法。你可以通过 ProviderContext 传递映射方法接收到的参数、“Mapper interface type” 和 “Mapper method”(仅在 MyBatis 3.4.5 以上支持)作为参数。(MyBatis 3.4 以上支持传入多个参数)属性:type、method。type 属性用于指定类名。method 用于指定该类的方法名(从版本 3.5.1 开始,可以省略 method 属性,MyBatis 将会使用 ProviderMethodResolver 接口解析方法的具体实现。如果解析失败,MyBatis 将会使用名为 provideSql 的降级实现)。提示 接下来的“SQL 语句构建器”一章将会讨论该话题,以帮助你以更清晰、更便于阅读的方式构建动态 SQL。
@Param参数N/A如果你的映射方法接受多个参数,就可以使用这个注解自定义每个参数的名字。否则在默认情况下,除 RowBounds 以外的参数会以 “param” 加参数位置被命名。例如 #{param1}, #{param2}。如果使用了 @Param(“person”),参数就会被命名为 #{person}。
@SelectKey方法<selectKey>这个注解的功能与 <selectKey> 标签完全一致。该注解只能在 @Insert 或 @InsertProvider 或 @Update 或 @UpdateProvider 标注的方法上使用,否则将会被忽略。如果标注了 @SelectKey 注解,MyBatis 将会忽略掉由 @Options 注解所设置的生成主键或设置(configuration)属性。属性:statement 以字符串数组形式指定将会被执行的 SQL 语句,keyProperty 指定作为参数传入的对象对应属性的名称,该属性将会更新成新的值,before 可以指定为 true 或 false 以指明 SQL 语句应被在插入语句的之前还是之后执行。resultType 则指定 keyProperty 的 Java 类型。statementType 则用于选择语句类型,可以选择 STATEMENT、PREPARED 或 CALLABLE 之一,它们分别对应于 Statement、PreparedStatement 和 CallableStatement。默认值是 PREPARED。
@ResultMap方法N/A这个注解为 @Select 或者 @SelectProvider 注解指定 XML 映射中 <resultMap> 元素的 id。这使得注解的 select 可以复用已在 XML 中定义的 ResultMap。如果标注的 select 注解中存在 @Results 或者 @ConstructorArgs 注解,这两个注解将被此注解覆盖。
@ResultType方法N/A在使用了结果处理器的情况下,需要使用此注解。由于此时的返回类型为 void,所以 Mybatis 需要有一种方法来判断每一行返回的对象类型。如果在 XML 有对应的结果映射,请使用 @ResultMap 注解。如果结果类型在 XML 的<select>元素中指定了,就不需要使用其它注解了。否则就需要使用此注解。比如,如果一个标注了 @Select 的方法想要使用结果处理器,那么它的返回类型必须是 void,并且必须使用这个注解(或者 @ResultMap)。这个注解仅在方法返回类型是 void 的情况下生效。
@Flush方法N/A如果使用了这个注解,定义在 Mapper 接口中的方法就能够调用 SqlSession#flushStatements() 方法。(Mybatis 3.3 以上可用)

  • 学习来自于西安加中实训
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@活着笑

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值