# mybatis学习 **Repository Path**: 2452860/mybatis-learning ## Basic Information - **Project Name**: mybatis学习 - **Description**: mybatis相关学习资料 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 0 - **Created**: 2021-05-11 - **Last Updated**: 2022-08-29 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # mybatis ## 参考文档 https://mybatis.org/mybatis-3/zh/index.html http://mybatis.org/spring/zh/boot.html http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/ ## 前言 > 在学习mybatis之前我们先来回顾一下原生的JDBC都做了些什么,为什么在有JDBC的前提下为还是出现了很多ORM持久层框架。 **JDBC编程三部曲** 1. 加载数据库驱动 2. 获取数据库连接对象 3. 创建Statement\PreparedStatement对象 ```java import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class DbUtil { public static final String URL = "jdbc:mysql:///test"; public static final String USER = "root"; public static final String PASSWORD = "123456"; public static void main(String[] args) throws Exception { //1.加载驱动程序 Class.forName("com.mysql.jdbc.Driver"); //2. 获得数据库连接 Connection conn = DriverManager.getConnection(URL, USER, PASSWORD); //3.操作数据库,实现增删改查 Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery("SELECT * FROM USER"); //如果有数据,rs.next()返回true while(rs.next()){ System.out.println(rs.getString("name")+" 年龄:"+rs.getInt("age")); } } } ``` **原生JDBC开发存在的问题** ![](img/image-20210508095259624.png) ## mybatis简介 为了解决原生JDBC开发过程中导致的问题,逐渐出现了很多持久层框架,这些持久层框架本质都是对JDBC进行了封装,通过封装来规避了JDBC开发中遇到的问题。mybatis就是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。mybatis免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。mybatis可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。 它将数据库连接的相关信息配置到文件当中,避免了JDBC硬编码的问题,它可以集成数据库连接池避免了频繁创建和释放数据库链接,消耗系统资源的问题,他将sql语句写入到配置文件中,解决了sq硬编码的问题,他通过复杂映射将查出来的数据直接注入到实体对象中,避免了自己组织返回参数的问题。 ## mybatis入门 ### 安装 > 要使用mybatis,只需要将mybatis的相关jar包引入工程即可,如果用maven构建项目,只需在POM文件下引入相应坐标即可。 ```xml org.mybatis mybatis x.x.x ``` ### 构建 每个基于MyBatis的应用都是以一个 SqlSessionFactory的实例为核心的。SqlSessionFactory的实例可以通过 SqlSessionFactoryBuilder 获得。而SqlSessionFactoryBuilder则可以从XML配置文件或一个预先配置的 Configuration实例来构建出SqlSessionFactory实例。 > 构建 SqlSessionFactory实例可以采用通过XML配置文件和JAVA代码进行构建,推荐使用XML构建。 #### 从XML中构建 从 XML 文件中构建 SqlSessionFactory的实例非常简单,建议使用类路径下的资源文件进行配置。 但也可以使用任意的输入流(InputStream)实例,比如用文件路径字符串或 file:// URL 构造的输入 流。MyBatis 包含一个名叫 Resources的工具类,它包含一些实用方法,使得从类路径或其它位置加载 资源文件更加容易。 ```java String resource = "mybatis-config.xml";//配置文件所在路径,相对于classpath路径 InputStream inputStream = Resources.getResourceAsStream(resource);//加载配置文件 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);//生产SqlSession工厂对象 ``` XML 文件中配置了关于mybatis工程的核心配置文件,包括了数据源相关的配置,起别名,加载properties文件以及加载SQL对应的mapper配置文件等信息。 #### 使用JAVA代码构建 > 如果你更愿意直接从 Java 代码而不是 XML 文件中创建配置,或者想要创建你自己的配置建造器, > MyBatis 也提供了完整的配置类,提供了所有与 XML 文件等价的配置项 。 ```java DataSource dataSource = BlogDataSourceFactory.getBlogDataSource(); TransactionFactory transactionFactory = new JdbcTransactionFactory(); Environment environment = new Environment("development", transactionFactory, dataSource); Configuration configuration = new Configuration(environment); configuration.addMapper(BlogMapper.class); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration); ``` ### XML配置文件 MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。配置文档的顶层结构如 下 > configuration(配置) > > > - properties(属性) > > > > - settings(设置) > > > > - typeAliases(类型别名) > > > > - typeHandlers(类型处理器) > > > > - objectFactory(对象工厂) > > > > - plugins(插件) > > > > - environments(环境配置) > > > > > - [ ] environment(环境变量) > > > - [ ] transactionManager(事务管理器) > > > - [ ] dataSource(数据源) > > > > - databaseIdProvider(数据库厂商标识) > > > > - mappers(映射器) #### properties(属性) 这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。例如: ```xml ``` 设置好的属性可以在整个配置文件中用来替换需要动态配置的属性值。比如: ```xml ``` > 如果配置文件中 jdbc.properties 包含了标签下的元素内容,那么标签下的元素内容将会被覆盖,同时SqlSessionFactoryBuilder.build() 的时候也可以传入属性值,如SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, props); 如果同一个属性在不同的地方做了配置,那么mybatis默认的优先级是 > > - 首先读取在 properties 元素体内指定的属性 > > - 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。 > > - 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。 > > 因此,通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性。 #### settings (属性) > seetings 属性是mybatis中极为重要的配置属性,它可以改变mybatis的运行行为 | 设置名 | 描述 | 有效值 | 默认值 | | -------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ----------------------------------------------------- | | cacheEnabled | 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。 | true \| false | true | | lazyLoadingEnabled | 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置 `fetchType` 属性来覆盖该项的开关状态。 | true \| false | false | | aggressiveLazyLoading | 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。否则,每个延迟加载属性会按需加载(参考 `lazyLoadTriggerMethods`)。 | true \| false | false (在 3.4.1 及之前的版本中默认为 true) | | multipleResultSetsEnabled | 是否允许单个语句返回多结果集(需要数据库驱动支持)。 | true \| false | true | | useColumnLabel | 使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察。 | true \| false | true | | useGeneratedKeys | 允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。 | true \| false | false | | autoMappingBehavior | 指定 MyBatis 应如何自动映射列到字段或属性。NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。 | NONE, PARTIAL, FULL | PARTIAL | | autoMappingUnknownColumnBehavior | 指定发现自动映射目标未知列(或未知属性类型)的行为。 `NONE`: 不做任何反应 `WARNING`: 输出警告日志(`'org.apache.ibatis.session.AutoMappingUnknownColumnBehavior'` 的日志等级必须设置为 `WARN`)`FAILING`: 映射失败 (抛出 `SqlSessionException`) | NONE, WARNING, FAILING | NONE | | defaultExecutorType | 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(PreparedStatement);BATCH 执行器不仅重用语句还会执行批量更新。 | SIMPLE REUSE BATCH | SIMPLE | | defaultStatementTimeout | 设置超时时间,它决定数据库驱动等待数据库响应的秒数。 | 任意正整数 | 未设置 (null) | | defaultFetchSize | 为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。 | 任意正整数 | 未设置 (null) | | defaultResultSetType | 指定语句默认的滚动策略。(新增于 3.5.2) | FORWARD_ONLY \| SCROLL_SENSITIVE \| SCROLL_INSENSITIVE \| DEFAULT(等同于未设置) | 未设置 (null) | | safeRowBoundsEnabled | 是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。 | true \| false | False | | safeResultHandlerEnabled | 是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为 false。 | true \| false | True | | mapUnderscoreToCamelCase | 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。 | true \| false | False | | localCacheScope | MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。默认值为 SESSION,会缓存一个会话中执行的所有查询。若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。 | SESSION \| STATEMENT | SESSION | | jdbcTypeForNull | 当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。某些数据库驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。 | JdbcType 常量,常用值:NULL、VARCHAR 或 OTHER。 | OTHER | | 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 的。 | true \| false | false | | returnInstanceForEmptyRow | 当返回行的所有列都是空时,MyBatis默认返回 `null`。当开启这个设置时,MyBatis会返回一个空实例。请注意,它也适用于嵌套的结果集(如集合或关联)。(新增于 3.4.2) | true \| false | false | | logPrefix | 指定 MyBatis 增加到日志名称的前缀。 | 任何字符串 | 未设置 | | logImpl | 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 | SLF4J \| LOG4J \| LOG4J2 \| JDK_LOGGING \| COMMONS_LOGGING \| STDOUT_LOGGING \| NO_LOGGING | 未设置 | | proxyFactory | 指定 Mybatis 创建可延迟加载对象所用到的代理工具。 | CGLIB \| JAVASSIST | JAVASSIST (MyBatis 3.3 以上) | | vfsImpl | 指定 VFS 的实现 | 自定义 VFS 的实现的类全限定名,以逗号分隔。 | 未设置 | | useActualParamName | 允许使用方法签名中的名称作为语句参数名称。为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 `-parameters` 选项。(新增于 3.4.1) | true \| false | true | | configurationFactory | 指定一个提供 `Configuration` 实例的类。这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。这个类必须包含一个签名为`static Configuration getConfiguration()` 的方法。(新增于 3.2.3) | 一个类型别名或完全限定类名。 | 未设置 | | shrinkWhitespacesInSql | 从SQL中删除多余的空格字符。请注意,这也会影响SQL中的文字字符串。 (新增于 3.5.5) | true \| false | false | | defaultSqlProviderType | Specifies an sql provider class that holds provider method (Since 3.5.6). This class apply to the `type`(or `value`) attribute on sql provider annotation(e.g. `@SelectProvider`), when these attribute was omitted. | A type alias or fully qualified class name | Not set | #### typeAliases (属性) > 类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。例如: ```xml ``` > 也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean ,比如: ```xml ``` #### typeHandlers (属性) >MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时,都会用类型处理器将获取到的值以合适的方式转换成 Java 类型 无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成 Java 类型。 通过类型处理器(TypeHandler),可以实现javaBean以某种方式存入数据库中,亦或是从数据库取出的数据如何映射为javaBean。 通过继承BaseTypeHandler类,我们可以定制这个类型处理器,已实现枚举类或是一个javaBean的存取和写入。 **处理枚举类型** 若想映射枚举类型 `Enum`,则需要从 `EnumTypeHandler` 或者 `EnumOrdinalTypeHandler` 中选择一个来使用。EnumTypeHandler存入数据库的是枚举的name,EnumOrdinalTypeHandler存入数据库的是枚举的位置。但是这两个并不好用,一般我们需要自己实现枚举的类型处理器。 #### objectFactory (属性) > 每次 MyBatis 创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成实例化工作。默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认无参构造方法,要么通过存在的参数映射来调用带有参数的构造方法。如果想覆盖对象工厂的默认行为,可以通过创建自己的对象工厂来实现。比如: ```java // ExampleObjectFactory.java public class ExampleObjectFactory extends DefaultObjectFactory { public Object create(Class type) { return super.create(type); } public Object create(Class type, List constructorArgTypes, List constructorArgs) { return super.create(type, constructorArgTypes, constructorArgs); } public void setProperties(Properties properties) { super.setProperties(properties); } public boolean isCollection(Class type) { return Collection.class.isAssignableFrom(type); }} ``` ```xml ``` > ObjectFactory 接口很简单,它包含两个创建实例用的方法,一个是处理默认无参构造方法的,另外一个是处理带参数的构造方法的。另外,setProperties 方法可以被用来配置 ObjectFactory,在初始化你的 ObjectFactory 实例后,objectFactory 元素体中定义的属性会被传递给 setProperties 方法。 #### plugins (属性) > MyBatis 允许你在映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括 > > - Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) > - ParameterHandler (getParameterObject, setParameters) > - ResultSetHandler (handleResultSets, handleOutputParameters) > - StatementHandler (prepare, parameterize, batch, update, query) **通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可**。 ```java @Intercepts({@Signature( type= Executor.class, method = "update", args = {MappedStatement.class,Object.class})}) public class ExamplePlugin implements Interceptor { private Properties properties = new Properties(); public Object intercept(Invocation invocation) throws Throwable { // implement pre processing if need Object returnObject = invocation.proceed(); // implement post processing if need return returnObject; } public void setProperties(Properties properties) { this.properties = properties; } } ``` ```xml ``` **如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。因为在试图修改或重写已有方法的行为时,很可能会破坏 MyBatis 的核心模块。这些都是更底层的类和方法,所以使用插件的时候要特别当心。** #### environments (属性) > MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中,现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中使用相同的 SQL 映射。还有许多类似的使用场景 **不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。** environments 元素定义了如何配置环境。 ```xml ``` > 注意一些关键点: > > - 默认使用的环境 ID(比如:default="development")。 > - 每个 environment 元素定义的环境 ID(比如:id="development")。 > - 事务管理器的配置(比如:type="JDBC")。 > - 数据源的配置(比如:type="POOLED")。 **事务管理器(transactionManager)** > 在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"): > > - JDBC – 这个配置直接使用了 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。 > - MANAGED – 这个配置几乎没做什么。它从不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。默认情况下它会关闭连接。然而一些容器并不希望连接被关闭,因此需要将 closeConnection 属性设置为 false 来阻止默认的关闭行为。例如: ```xml ``` *如果你使用了Sping来集成mybatis框架,你不需要配置事务管理器,应该将其交于Spring进行管理* **数据源(dataSource)** > dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源 > > 大多数 MyBatis应用程序会按示例中的例子来配置数据源。虽然数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。 > > 有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]") > > **UNPOOLED**– 这个数据源的实现会每次请求时打开和关闭连接。虽然有点慢,但对那些数据库连接可用性要求不高的简单应用程序来说,是一个很好的选择。性能表现则依赖于使用的数据库,对某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。UNPOOLED 类型的数据源仅仅需要配置以下 5 种属性: > > - `driver` – 这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能包含的数据源类)。 > - `url` – 这是数据库的 JDBC URL 地址。 > - `username` – 登录数据库的用户名。 > - `password` – 登录数据库的密码。 > - `defaultTransactionIsolationLevel` – 默认的连接事务隔离级别。 > - `defaultNetworkTimeout` – 等待数据库操作完成的默认网络超时时间(单位:毫秒)。查看 `java.sql.Connection#setNetworkTimeout()` 的 API 文档以获取更多信息。 > > 作为可选项,你也可以传递属性给数据库驱动。只需在属性名加上“driver.”前缀即可,例如: > > - `driver.encoding=UTF8` > > 这将通过 DriverManager.getConnection(url, driverProperties) 方法传递值为 `UTF8` 的 `encoding` 属性给数据库驱动。 > > **POOLED**– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。这种处理方式很流行,能使并发 Web 应用快速响应请求。 > > 除了上述提到 UNPOOLED 下的属性外,还有更多属性用来配置 POOLED 的数据源: > > - `poolMaximumActiveConnections` – 在任意时间可存在的活动(正在使用)连接数量,默认值:10 > - `poolMaximumIdleConnections` – 任意时间可能存在的空闲连接数。 > - `poolMaximumCheckoutTime` – 在被强制返回之前,池中连接被检出(checked out)时间,默认值:20000 毫秒(即 20 秒) > - `poolTimeToWait` – 这是一个底层设置,如果获取连接花费了相当长的时间,连接池会打印状态日志并重新尝试获取一个连接(避免在误配置的情况下一直失败且不打印日志),默认值:20000 毫秒(即 20 秒)。 > - `poolMaximumLocalBadConnectionTolerance` – 这是一个关于坏连接容忍度的底层设置, 作用于每一个尝试从缓存池获取连接的线程。 如果这个线程获取到的是一个坏的连接,那么这个数据源允许这个线程尝试重新获取一个新的连接,但是这个重新尝试的次数不应该超过 `poolMaximumIdleConnections` 与 `poolMaximumLocalBadConnectionTolerance` 之和。 默认值:3(新增于 3.4.5) > - `poolPingQuery` – 发送到数据库的侦测查询,用来检验连接是否正常工作并准备接受请求。默认是“NO PING QUERY SET”,这会导致多数数据库驱动出错时返回恰当的错误消息。 > - `poolPingEnabled` – 是否启用侦测查询。若开启,需要设置 `poolPingQuery` 属性为一个可执行的 SQL 语句(最好是一个速度非常快的 SQL 语句),默认值:false。 > - `poolPingConnectionsNotUsedFor` – 配置 poolPingQuery 的频率。可以被设置为和数据库连接超时时间一样,来避免不必要的侦测,默认值:0(即所有连接每一时刻都被侦测 — 当然仅当 poolPingEnabled 为 true 时适用)。 > > **JNDI** – 这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。这种数据源配置只需要两个属性: > > - `initial_context` – 这个属性用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))。这是个可选属性,如果忽略,那么将会直接从 InitialContext 中寻找 data_source 属性。 > - `data_source` – 这是引用数据源实例位置的上下文路径。提供了 initial_context 配置时会在其返回的上下文中进行查找,没有提供时则直接在 InitialContext 中查找。 > > 和其他数据源配置类似,可以通过添加前缀“env.”直接把属性传递给 InitialContext。比如: > > - `env.encoding=UTF8` > > 这就会在 InitialContext 实例化时往它的构造方法传递值为 `UTF8` 的 `encoding` 属性。 > > 你可以通过实现接口 `org.apache.ibatis.datasource.DataSourceFactory` 来使用第三方数据源实现 #### databaseIdProvider(属性) > MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 `databaseId` 属性。 MyBatis 会加载带有匹配当前数据库 `databaseId` 属性和所有不带 `databaseId` 属性的语句。 如果同时找到带有 `databaseId` 和不带 `databaseId` 的相同语句,则后者会被舍弃。 为支持多厂商特性,只要像下面这样在 mybatis-config.xml 文件中加入 `databaseIdProvider` 即可: ```xml ``` #### mappers(属性) > 告诉 MyBatis 到哪里去找sql语句, 你可以使用相对于类路径的资源引用,或完全限定资源定位符(包括 `file:///` 形式的 URL),或类名和包名等。例如: ```xml ``` **注意当使用映射器接口实现类的完全限定类名和将包内的映射器接口实现全部注册为映射器加载SQL语句的配置文件时,XML配置文件和mapeer类必需在统一报名下,否则报错** ## mybatis 应用 mybtais 使用可以基于XML配置文件开发,也可以基于注解开发,但是XML配置文件更加灵活,适用于大多数复杂查询的结果映射,而注解开发起来快,但是针对复杂sql语句映射则显得力不从心,需要使用什么样的开发形式需要按照项目规模和业务的复杂程度而定,针对于一般小项目推荐使用注解进行开发,其他情况则使用XML配置文件形式进行开发。 ### 相关准备 1. **创建maven项目,导入相关jar包** ```xml org.mybatis mybatis 3.4.5 mysql mysql-connector-java 5.1.6 runtime junit junit 4.12 test log4j log4j 1.2.12 ``` 2. **创建数据库** ```sql DROP TABLE IF EXISTS `t_order`; CREATE TABLE `t_order` ( `id` int(16) NOT NULL, `order_time` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `uid` int(16) NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; DROP TABLE IF EXISTS `t_role`; CREATE TABLE `t_role` ( `id` int(16) NOT NULL, `role_name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; DROP TABLE IF EXISTS `t_user`; CREATE TABLE `t_user` ( `id` int(16) NOT NULL, `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; DROP TABLE IF EXISTS `t_user_role`; CREATE TABLE `t_user_role` ( `id` int(16) NOT NULL AUTO_INCREMENT, `uid` int(16) NULL DEFAULT NULL, `rid` int(16) NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; ``` 3. **创建mybatis-config.xml配置文件** ```xml ``` 4. **创建 jdbc.properties 配置文件** ```properties jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql:///test?useUnicode=true&characterEncoding=utf-8 jdbc.username=root jdbc.password=123456 ``` ### 单表数据库增删改查 > mybatis 开发可以通过写dao层接口的实现类,通过sqlSession 传入相应的statementid 调用不同的实现方法进行开发,但这种方式有大量的冗余代码,还必须传递statementid 作为参数,导致存在硬编码问题;还可以通过getMapper方式进行开发,这种方式灵活,底层通过JDK动态代理的方式隐藏了具体实现,但注意此时mapper 的 namespace 需要配置为mapper接口的全限定类名 sql的id配置为mapper相应的方法名,一般在开发中采用第二种方式实现,第一种实现方式不在单独演示。 #### 基于XML配置文件 ##### 新增 > 创建user实体类 ```java package com.h52mm.mybatis.demo.model; public class User { private Integer id; private String userName; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } @Override public String toString() { return "User{" + "id=" + id + ", userName='" + userName + '\'' + '}'; } } ``` > 创建UserMapper接口 ```java package com.h52mm.mybatis.demo.mapper; import com.h52mm.mybatis.demo.model.User; import java.util.List; public interface UserMapper { public void addUser(User user); } ``` > 创建UserMapper.xml 文件 ```xml insert into t_user(id,username) values(#{id},#{userName}) ``` > 创建测试方法进行测试 ```java @Test public void addUser() throws IOException { //加载配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); //构建sqlSessionFactory类 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //获取sqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User u=new User(); u.setId(1); u.setUserName("张三"); userMapper.addUser(u); //提交事务 sqlSession.commit(); } ``` ##### 修改 > 在UserMapper接口中新增修改的方法 ```java public void updateUser(User user); ``` > 在UserMapper.xml 文件中新增配置 ```xml update t_user set username=#{userName} where id=#{id} ``` > 新建测试方法 ```java @Test public void updateUser() throws IOException { //加载配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); //构建sqlSessionFactory类 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //获取sqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User u=new User(); u.setId(1); u.setUserName("zhang san"); userMapper.updateUser(u); //提交事务 sqlSession.commit(); } ``` ##### 删除 > 在UserMapper 接口中新增方法 ```java public void deleteUserById(Integer id); ``` > 在UserMapper.xml 文件中新增删除配置 ```xml delete from t_user where id=#{id} ``` > 新增测试方法 ```java @Test public void deleteUser() throws IOException { //加载配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); //构建sqlSessionFactory类 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //获取sqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); userMapper.deleteUserById(1); //提交事务 sqlSession.commit(); } ``` ##### 查询 > 在UserMapper中新增查找的方法 ```java public List selectUsers(User user); public User selectUserById(Integer id); ``` > 在UserMapper.xml 中配置查询 ```xml ``` #### 基于注解 ##### 新增 > 创建UserZjMapper 接口 ```java package com.h52mm.mybatis.demo.mapper; import com.h52mm.mybatis.demo.model.User; import org.apache.ibatis.annotations.Insert; public interface UserZjMapper { @Insert("insert into t_user(id,username) values(#{id},#{userName})") public void addUser(User user); } ``` > 创建测试方法 ```java @Test public void addUserZj() throws IOException { //加载配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); //构建sqlSessionFactory类 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //获取sqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); UserZjMapper userMapper = sqlSession.getMapper(UserZjMapper.class); User user =new User(); user.setId(1); user.setUserName("张三"); userMapper.addUser(user); //提交事务 sqlSession.commit(); } ``` ##### 修改 > 在UserZjMapper 接口中新增修改方法 ```java @Update("update t_user set username=#{userName} where id=#{id} ") public void updateUser(User user); ``` > 创建测试方法 ```java @Test public void updateUserZj() throws IOException { //加载配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); //构建sqlSessionFactory类 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //获取sqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); UserZjMapper userMapper = sqlSession.getMapper(UserZjMapper.class); User user =new User(); user.setId(1); user.setUserName("zhang san"); userMapper.updateUser(user); //提交事务 sqlSession.commit(); } ``` ##### 删除 > 在UserZjMapper接口中新增方法 ```java @Delete("delete from t_user where id=#{id}") public void deleteUserById(Integer id); ``` > 新增测试方法 ```java @Test public void deleteUserByIdZj() throws IOException { //加载配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); //构建sqlSessionFactory类 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //获取sqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); UserZjMapper userMapper = sqlSession.getMapper(UserZjMapper.class); userMapper.deleteUserById(1); //提交事务 sqlSession.commit(); } ``` ##### 查询 > 在UserZjMapper 接口中新增查询方法 ```java @Select("") public List selectUsers(User user); @Select("select id,username userName from t_user where id=#{id}") public User selectUserById(Integer id); ``` > 新增测试方法 ```java @Test public void selectUsersZj() throws IOException { //加载配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); //构建sqlSessionFactory类 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //获取sqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); UserZjMapper userMapper = sqlSession.getMapper(UserZjMapper.class); User user=new User(); user.setId(1); user.setUserName("张三"); List users = userMapper.selectUsers(user); for (User user1 : users) { System.out.println(user1); } } @Test public void selectUserZj() throws IOException { //加载配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); //构建sqlSessionFactory类 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //获取sqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); UserZjMapper userMapper = sqlSession.getMapper(UserZjMapper.class); User user = userMapper.selectUserById(1); System.out.println(user); } ``` ### 复杂查询映射 #### 前提准备 ```sql insert into t_user values(2,'李四'); insert into t_order(id,order_time,uid) values(1,'20210410113301',1); insert into t_order(id,order_time,uid) values(2,'20210410113501',1); insert into t_order(id,order_time,uid) values(3,'20210410113501',2); insert into t_role(id,role_name) values(1,'角色1'); insert into t_role(id,role_name) values(2,'角色2'); insert into t_user_role(id,uid,rid) values(1,1,1); insert into t_user_role(id,uid,rid) values(2,1,2); insert into t_user_role(id,uid,rid) values(3,2,1); ``` #### 基于XML > 说明: > > SQL 映射文件顶级元素(按照应被定义的顺序列出) > > - `cache` – 该命名空间的缓存配置。 > - `cache-ref` – 引用其它命名空间的缓存配置。 > - `resultMap` – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。 > - `parameterMap` – 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。 > - `sql` – 可被其它语句引用的可重用语句块。 > - `insert` – 映射插入语句。 > - `update` – 映射更新语句。 > - `delete` – 映射删除语句。 > - `select` – 映射查询语句。 > > 这里重点讲解 `resultMap` 元素 > > **resultMap** > > **属性** > > - `id ` 当前命名空间中的一个唯一标识,用于标识一个结果映射。 > - `type` 类的完全限定名, 或者一个类型别名 > - `autoMapping` 如果设置这个属性,MyBatis 将会为本结果映射开启或者关闭自动映射。这个属性会覆盖全局的属性 autoMappingBehavior。 > > **元素** > > - `constructor` - 用于在实例化类时,注入结果到构造方法中 > - `idArg` - ID 参数;标记出作为 ID 的结果可以帮助提高整体性能 > - `arg` - 将被注入到构造方法的一个普通结果 > - `id` – 一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能 (对应数据库表中主键) > - `result` – 注入到字段或 JavaBean 属性的普通结果 > - `association` – 一个复杂类型的关联;许多结果将包装成这种类型 (用于一对一的关联关系查询) > > - 嵌套结果映射 – 关联可以是 `resultMap` 元素,或是对其它结果映射的引用 > - `collection` – 一个复杂类型的集合 (用于一对多或者多对多的关联关系查询中) > > - 嵌套结果映射 – 集合可以是 `resultMap` 元素,或是对其它结果映射的引用 > - `discriminator` – 使用结果值来决定使用哪个 `resultMap` > - `case` – 基于某些值的结果映射 > - 嵌套结果映射 – `case` 也是一个结果映射,因此具有相同的结构和元素;或者引用其它的结果映射 ##### 一对一 > 创建User 和 Order 实体对象 ```java package com.h52mm.mybatis.demo.model; public class User { private Integer id; private String userName; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } @Override public String toString() { return "User{" + "id=" + id + ", userName='" + userName + '\'' + '}'; } } ``` ```java package com.h52mm.mybatis.demo.model; public class Order { private Integer id; private String orderTime; private User user; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getOrderTime() { return orderTime; } public void setOrderTime(String orderTime) { this.orderTime = orderTime; } public User getUser() { return user; } public void setUser(User user) { this.user = user; } @Override public String toString() { return "Order{" + "id=" + id + ", orderTime='" + orderTime + '\'' + ", user=" + user + '}'; } } ``` > 创建OrderMapper 接口 ```java package com.h52mm.mybatis.demo.mapper; import com.h52mm.mybatis.demo.model.Order; import java.util.List; public interface OrderMapper { public List selectOrderList(); } ``` > 创建 OrderMapper.xml 配置文件 ```xml ``` > 创建测试方法 ```java package com.h52mm.mybatis.demo.test; import com.h52mm.mybatis.demo.mapper.OrderMapper; import com.h52mm.mybatis.demo.mapper.UserMapper; import com.h52mm.mybatis.demo.mapper.UserZjMapper; import com.h52mm.mybatis.demo.model.Order; import com.h52mm.mybatis.demo.model.User; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.junit.Test; import java.io.IOException; import java.io.InputStream; import java.util.List; public class TestOrderMybatis { @Test public void selectOrders() throws IOException { //加载配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); //构建sqlSessionFactory类 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //获取sqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); OrderMapper orderMapper = sqlSession.getMapper(OrderMapper.class); List orders = orderMapper.selectOrderList(); for (Order order : orders) { System.out.println(order); } } } ``` ##### 一对多 > 改造User 类,在里面增加List orders 成员变量,并设置get/set方法,并且重写toString 方法 ```java package com.h52mm.mybatis.demo.model; import java.util.List; public class User { private Integer id; private String userName; private List orders; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public List getOrders() { return orders; } public void setOrders(List orders) { this.orders = orders; } @Override public String toString() { return "User{" + "id=" + id + ", userName='" + userName + '\'' + ", orders=" + orders + '}'; } } ``` > 在UserMapeer接口中新增查询接口 ```java public List selectUserOrders(); ``` > 在UserMapper.xml 文件中新增如下配置 ```xml ``` > 创建测试方法 ```java @Test public void selectUserOrders() throws IOException { //加载配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); //构建sqlSessionFactory类 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //获取sqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List users = userMapper.selectUserOrders(); for (User user : users) { System.out.println(user); } } ``` ##### 多对多 > 多对多的关联关系,在表结构上一般是通过中间表来建立关联关系的,实现多对多关系本质上就是将其转化为两个一对多的关系,因此其实现和一对多关联关系是一样的,所不同的是在于sql语句的不同。 > 创建Role实体类 ```java package com.h52mm.mybatis.demo.model; import java.util.List; public class Role { private Integer id; private String roleName; List users; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } public List getUsers() { return users; } public void setUsers(List users) { this.users = users; } @Override public String toString() { return "Role{" + "id=" + id + ", roleName='" + roleName + '\'' + ", users=" + users + '}'; } } ``` > 创建 RoleMapper 接口 ```java package com.h52mm.mybatis.demo.mapper; import com.h52mm.mybatis.demo.model.Role; import java.util.List; public interface RoleMapper { public List selectRoleUsers(); } ``` > 创建 RoleMapper.xml 配置文件 ```xml ``` > 创建测试方法 ```java @Test public void selectRoleUsers() throws IOException { //加载配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); //构建sqlSessionFactory类 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //获取sqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class); List roles = roleMapper.selectRoleUsers(); for (Role role : roles) { System.out.println(role); } } ``` #### 基于注解 > 基于注解实现 > > Results 注解相当于XML配置文件中的resultMap ,但是又不太一样,注解实现关联查询,是将sql拆开执行的,所以当sql关联关系比较复杂的时候,这种注解开发的模式将不太适用。 > > Result 注解相当于 xml中的result 标签 > > @One 一对一 > > @Many 一对多或多对多 ##### 一对一 > 创建 OrderZjMapper 接口 ```java package com.h52mm.mybatis.demo.mapper; import com.h52mm.mybatis.demo.model.Order; import org.apache.ibatis.annotations.One; import org.apache.ibatis.annotations.Result; import org.apache.ibatis.annotations.Results; import org.apache.ibatis.annotations.Select; import java.util.List; public interface OrderZjMapper { @Results({ @Result(property = "id",column = "id"), @Result(property = "orderTime",column = "order_time"), @Result(property = "user" ,column = "uid" ,one = @One(select = "com.h52mm.mybatis.demo.mapper.UserZjMapper.selectUserById")) }) @Select("select *from t_order") public List selectOrderList(); } ``` **@One(select = "com.h52mm.mybatis.demo.mapper.UserZjMapper.selectUserById") 这个标识拿着colunm查出的数据从UserZjMapper.selectUserById 这个方法中去查询用户信息** > UserZjMapper 中必需存在方法 selectUserById ```java @Select("select id,username userName from t_user where id=#{id}") public User selectUserById(Integer id); ``` > 创建测试方法 ```java @Test public void selectZjOrders() throws IOException { //加载配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); //构建sqlSessionFactory类 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //获取sqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); OrderZjMapper orderMapper = sqlSession.getMapper(OrderZjMapper.class); List orders = orderMapper.selectOrderList(); for (Order order : orders) { System.out.println(order); } } ``` ##### 一对多 > 在UserZjMapper 接口中新增 ```java @Results({ @Result(property = "id",column = "id"), @Result(property = "userName",column = "userName"), @Result(property = "orders" ,column = "id",many = @Many(select="com.h52mm.mybatis.demo.mapper.OrderZjMapper.selectOrdersByUid")) }) @Select("select id,username userName from t_user ") public List selectUserOrders(); ``` > 在OrderZjMapper 中新增 ```java @Select("select id,order_time orderTime from t_order where uid=#{uid}") public List selectOrdersByUid(Integer uid); ``` > 新增测试方法 ```java @Test public void selectZjUserOrders() throws IOException { //加载配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); //构建sqlSessionFactory类 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //获取sqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); UserZjMapper userMapper = sqlSession.getMapper(UserZjMapper.class); List users = userMapper.selectUserOrders(); for (User user : users) { System.out.println(user); } } ``` ##### 多对多 > 新建RoleZjMapper 接口 ```java package com.h52mm.mybatis.demo.mapper; import com.h52mm.mybatis.demo.model.Role; import org.apache.ibatis.annotations.Many; import org.apache.ibatis.annotations.Result; import org.apache.ibatis.annotations.Results; import org.apache.ibatis.annotations.Select; import java.util.List; public interface RoleZjMapper { @Results({ @Result(property = "id",column = "id"), @Result(property = "roleName",column = "roleName"), @Result(property = "users",column = "id",many = @Many(select = "com.h52mm.mybatis.demo.mapper.UserZjMapper.selectUsersByRid")) }) @Select("select id,role_name roleName from t_role") public List selectRoleUsers(); } ``` > UserZjMapper 接口中新增 ```java @Select("select u.id id,u.username userName from t_user u LEFT JOIN t_user_role o on u.id=o.uid where o.rid=#{rid}") public List selectUsersByRid(Integer rid); ``` > 新增测试方法 ```java @Test public void selectRoleUsersZj() throws IOException { //加载配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); //构建sqlSessionFactory类 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //获取sqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); RoleZjMapper roleMapper = sqlSession.getMapper(RoleZjMapper.class); List roles = roleMapper.selectRoleUsers(); for (Role role : roles) { System.out.println(role); } } ``` ### 动态SQL > 在实际开发中我们会遇到根据前台传的不通的参数来对数据进行查询,这过程我们会根据参数拼接相应的查询条件,这个功能在mybtais中可以采用动态sql来进行实现。 > > 动态 SQL 是 MyBatis 的强大特性之一,它包含了如下元素 > > - if > - choose (when, otherwise) > - trim (where, set) > - foreach #### if > 使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。比如: ```xml ``` > 这条语句提供了可选的查找文本功能,如果userName不传入,那么会查询所有的用户信息,userName传入了,则会根据userName查找用户信息。 #### choose、when、otherwise > 有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用,这个时候就可以使用 choose、when、otherwise 的组合,如: ```xml ``` #### trim、where、set > 现在我们来回顾if里面的语句,里面包含了 where 1=1 的语句,这句话主要是为了防止后面拼接参数导致语法错误,如果语句中不写这个 where 1=1 就会导致语法错误,这个时候就要用到 where 标签来解决这个问题。上面 if 里面的查询可以改为 ```java ``` > set 标签是在更新操作的时候,有些字段可以动态的设置需不需要更新,如下 ```xml update Author username=#{username}, password=#{password}, email=#{email}, bio=#{bio}, where id=#{id} ``` > trim 标签也可以实现 where 标签或者set 标签的功能 ```xml ... ... ``` #### foreach > 动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候),foreach 就是实现这个功能的。 ```xml ``` **你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 *foreach*。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值** #### script > 基于注解开发时想要实现动态sql,可以用该标签包裹sql语句,如 ```java @Select({""}) public List selectUsers(User user); ``` #### bind > `bind` 元素允许你在 OGNL 表达式以外创建一个变量,并将其绑定到当前的上下文。比如: ```xml ``` ### 存储过程 #### 准备 ```mysql DELIMITER $$ CREATE PROCEDURE test_procedure (IN p_id int,IN p_username VARCHAR(100),OUT p_msg VARCHAR(100)) begin insert into t_user(id,username) values(p_id,p_username); set p_msg='新增成功'; select p_msg; end $$ DELIMITER; ``` #### 测试范例 > 在User 类下新增msg 字段,并设置get/set方法,最后重写toString方法 ```java package com.h52mm.mybatis.demo.model; import java.util.List; public class User { private Integer id; private String userName; private String msg; private List orders; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public List getOrders() { return orders; } public void setOrders(List orders) { this.orders = orders; } public String getMsg() { return msg; } public void setMsg(String msg) { this.msg = msg; } @Override public String toString() { return "User{" + "id=" + id + ", userName='" + userName + '\'' + ", msg='" + msg + '\'' + '}'; } } ``` > 在UserMapper.xml 文件下新增配置 ```xml { call test_procedure(#{id,mode=IN},#{userName,mode=IN},#{msg,mode=OUT,jdbcType=VARCHAR}) } ``` **statementType必需配置为CALLABLE,否则会报错** > 新增测试方法 ```java @Test public void testProcedure() throws IOException { //加载配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml"); //构建sqlSessionFactory类 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream); //获取sqlSession对象 SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user=new User(); user.setId(3); user.setUserName("刘豪"); userMapper.addUserBYProcedure(user); System.out.println(user); } ``` ### 缓存 > mybatis 内部提供了二级缓存和一级缓存的功能,其中一级缓存是默认开启的,开启二级缓存在默认配置下只需要在mapeer配置文件下增加`cache` 标签即可。其中一级缓存是sqlsession 级别的,也就是说是方法级别的,方法执行完毕,缓存就会失效。二级缓存可以跨sqlsession共享,是namespace级别的缓存,但是其线程是不安全的,在分布式环境或者跨namespaces时会出现脏读问题,不推荐使用。 > > mybatis缓存级别:二级缓存 > 一级缓存 > > 二级缓存生效是在sqlsession 执行commit方法或者close方法后才会生效 > > 一级缓存和二级缓存在执行了insert update delete 方法后会将缓存清空 **总结mybatis的缓存机制设计有缺陷(存在脏读问题),一般不使用** #### 一级缓存 ![](img/image-20210510173309023.png) > 一级缓存基于PrepetualCache的HashMap本地缓存,其存储作用域为Session,位于表示一次数据库会话的**SqlSession**对象之中。 > > MyBatis会在表示会话的**SqlSession**对象中建立一个简单的缓存,将每次查询到的结果结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了,增加了查询效率,减少数据库压力。 #### 二级缓存 ![](img/image-20210510173403147.png) > 二级缓存和一级缓存的机制相同,默认也是采用PrepetualCache、HashMap存储,但是二级缓存是Application应用级别的缓存,它的生命周期很长,跟Application的生命周期一样,也就是说它的作用范围是整个Application应用。但Application不是只有一个Cache缓存对象,它将缓存划分的更细,即Mapper(nameSpace)级别的,每一个Mapper都可以拥有一个Cache对象 **如果你想让多个Mapper公用一个Cache的话,你可以使用节点,来指定你的这个Mapper使用到了哪一个Mapper的Cache缓存** > 开启二级缓存的条件 > > - 全局配置(mybatis-config.xml)变量参数 **cacheEnabled=true** > > - Mapper配置文件中配置了节点,并且有效 > > - select语句的参数 useCache=true > > - 实体类必须实现序列化接口 > > **以上条件缺一不可,全局配置文件中cacheEnabled 默认为true select语句的参数 useCache 默认也是true** ### Spring 集成 Mybatis > 在项目开发中我们一般会用spring 框架整合mybtais,将mybatis相关对象的创建交于spring 进行管理,这里要用到 MyBatis-Spring 这个项目。 #### 什么是Mybatis-Spring > MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 `SqlSession` 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 `DataAccessException`。 最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。 #### 使用MyBatis-Spring > 使用MyBatis-Spring 只需要在项目中引入MyBatis-Spring 的jar依赖即可,如果用maven构建项目,可以在POM.xml 文件中引入如下坐标 ```xml org.mybatis mybatis-spring 2.0.6 ``` #### 整合Spring > 要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 `SqlSessionFactory` 和至少一个数据映射器类。 > > 在 MyBatis-Spring 中,可使用 `SqlSessionFactoryBean`来创建 `SqlSessionFactory`。 要配置这个工厂 bean,只需要把下面代码放在 Spring 的 XML 配置文件中: ```xml ``` **`mapperLocations` 属性接受多个资源位置。这个属性可以用来指定 MyBatis 的映射器 XML 配置文件的位置。属性的值是一个 Ant 风格的字符串,可以指定加载一个目录中的所有文件,或者从一个目录开始递归搜索所有目录** > 或者通过配置类引入 ```java @Configuration public class MyBatisConfig { @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource()); return factoryBean.getObject(); } } ``` **注意:`SqlSessionFactory` 需要一个 `DataSource`(数据源)。这可以是任意的 `DataSource`,只需要和配置其它 Spring 数据库连接一样配置它就可以了。可以采用C3P0、druid等数据库连接池** >通过 `MapperScannerConfigurer` 将接口加入到 Spring 中 ```xml ``` > 配置事务管理器 一个使用 MyBatis-Spring 的其中一个主要原因是它允许 MyBatis 参与到 Spring 的事务管理中。而不是给 MyBatis 创建一个新的专用事务管理器,MyBatis-Spring 借助了 Spring 中的 `DataSourceTransactionManager` 来实现事务管理。 要开启 Spring 的事务处理功能,在 Spring 的配置文件中创建一个 `DataSourceTransactionManager` 对象: ```xml ``` #### 整合 Spring boot > 引入依赖 ```xml org.mybatis.spring.boot mybatis-spring-boot-starter 2.1.3 ``` **整合之前需要先配置数据库连接池和声明性事务** > 在application.yml 配置文件中配置 ```xml mybatis: mapper-locations: classpath:com/h52mm/mybatis/demo/mapper/**/*.xml config-locations: classpath:mybatis-config.xml type-aliases-package: com.h52mm.mybatis.demo.mapper ``` > 启动类上增加注解 ```java @MapperScan("com.h52mm.mybatis.demo.mapper") ```