MyBatis(一)——入门

Mybatis文档是非常好的学习资料,最重要的是支持中文,比谷歌翻译好【你懂的】,但是,有些东西,还是不是很理解,所以,就写了这篇博客记录一下。

文章目录

    • 1. Mybatis 介绍
      • 1.1 为什么要使用Mybatis
      • 1.2 Jdbc存在的问题
      • 1.3 Mybatis 是什么?
    • 2. Mybatis入门程序
    • 4. 全局配置文件详解
      • 3.1 引入外部配置文件
      • 3.2 运行时设置(settings)
      • 3.3 别名配置(typeAliases)
      • 3.4 类型处理器简介(typeHandlers)
      • 3.5 插件简介(plugins)
      • 3.6 运行环境(enviroments)
      • 3.7 多数据库支持(databaseIdProvider)
      • 3.8 映射器(mappers)
    • 4. Mybatis Dao的开发方式
      • 4.1 传统方式
      • 4.2. 接口式编程
    • 5. XML 映射文件
      • 5.1 增删改查标签中常用的属性
        • 5.1.1 parameterType
        • 5.1.2 resultType
        • 5.1.3 resultMap
        • 5.1.4 获取自增主键的值:
      • 5.2 参数
      • 5.3 参数处理源码分析
      • 5.4 获取参数的值
      • 5.5 结果映射

1. Mybatis 介绍

1.1 为什么要使用Mybatis

我们先来回顾一下使用JDBC访问数据库的过程:

  1. 加载数据库驱动
  2. 创建数据库连接
  3. 创建statement
  4. 设置sql语句
  5. 设置查询参数
  6. 执行查询,得到ResultSet
  7. 解析结果集ResultSet
  8. 释放资源

1.2 Jdbc存在的问题

  1. 频繁创建和打开、关闭数据连接,太消耗资源
  2. Sql语句存在硬编码,不利于维护
  3. Sql参数设置硬编码,不利于维护
  4. 结果集获取与遍历复杂,存在硬编码,不利于维护,期望能够查询后返回一个java对象

1.3 Mybatis 是什么?

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。

MyBatis是一个半自动化的持久层框架。

  • 对开发人员而言,核心sql还是需要自己优化
  • sql和java编码分开,功能边界清晰,一个专注业务、一个专注数据。

Mybatis官网 (强烈推荐)

2. Mybatis入门程序

1、创建数据库及表

CREATE TABLE employee(
	id INT(11) PRIMARY KEY AUTO_INCREMENT,
	last_name VARCHAR(255),
	gender CHAR(1),
	email VARCHAR(255)
);

2、创建Maven工程,添加依赖

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

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>6.0.6</version>
</dependency>

3、创建对应的JavaBean

public class Employee {
	
	private Integer id;
	private String lastName;
	private String email;
	private String gender;

	//getter and setter and toString()
}

4、创建mybatis配置文件,sql映射文件

  • MyBatis 的全局配置文件包含了影响 MyBatis 行为甚深的设置( settings)和属性( properties)信息、如数据库连接池信息等。指导着MyBatis进行工作。我们可以参照官方文件的配置示例。
  • 映射文件的作用就相当于是定义Dao接口的实现类如何工作。这也是我们使用MyBatis时编写的最多的文件。

全局配置文件(mybatis-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>
	<!--和spring整合后 environments配置将废除 -->
	<environments default="test">
		<environment id="test">
			<!--使用jdbc事务管理 -->
			<transactionManager type="JDBC"/>
			<!--数据库连接池 -->
			<dataSource type="POOLED">
				<property name="driver" value="com.mysql.jdbc.Driver"/>
				<property name="url"
					value="jdbc:mysql://localhost:3306/mybaits01?characterEncoding=utf-8"/>
				<property name="username" value="root"/>
				<property name="password" value="root"/>
			</dataSource>
		</environment>
	</environments>
	
	<!-- 加载映射文件 -->
	<mappers>
		<mapper resource="EmployeeMapper.xml"/>
	</mappers>
</configuration>

sql映射文件(EmployeeMapper.xml)

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="abc">
<!-- 
namespace:名称空间;通常指定为接口的全类名
id:唯一标识
resultType:返回值类型
#{id}:从传递过来的参数中取出id值

public Employee getEmpById(Integer id);
-->
	<select id="getEmpById" resultType="com.lun.c01.helloworld.bean.Employee">
		select id,last_name lastName,email,gender from employee where id = #{id}
	</select>
</mapper>

这里只写了查询,更新、删除、插入和这类似,只需换成相应的标签和语句即可。

5、测试

public class HelloWorldTest {

	public SqlSessionFactory getSqlSessionFactory() throws IOException {
		String resource = "c01/mybatis-config.xml";
		//加载SqlMapConfig.xml文件
		InputStream inputStream = Resources.getResourceAsStream(resource);
		//创建SqlSession对象,并返回
		return new SqlSessionFactoryBuilder().build(inputStream);
	}

	/**
	 * 1、根据xml配置文件(全局配置文件)创建一个SqlSessionFactory对象 有数据源一些运行环境信息
	 * 2、sql映射文件;配置了每一个sql,以及sql的封装规则等。 
	 * 3、将sql映射文件注册在全局配置文件中
	 * 4、写代码:
	 * 		1)、根据全局配置文件得到SqlSessionFactory;
	 * 		2)、使用sqlSession工厂,获取到sqlSession对象使用他来执行增删改查
	 * 			一个sqlSession就是代表和数据库的一次会话,用完关闭
	 * 		3)、使用sql的唯一标志来告诉MyBatis执行哪个sql。sql都是保存在sql映射文件中的。
	 * 
	 * @throws IOException
	 */
	@Test
	public void test() throws IOException {

		// 2、获取sqlSession实例,能直接执行已经映射的sql语句
		// sql的唯一标识:statement Unique identifier matching the statement to use.
		// 执行sql要用的参数:parameter A parameter object to pass to the statement.
		SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

		SqlSession openSession = sqlSessionFactory.openSession();
		try {
			Employee employee = openSession.selectOne(
					"abc.getEmpById", 1);
			System.out.println(employee);
		} finally {
			openSession.close();
		}
	}
}

4. 全局配置文件详解

MyBatis 的配置文件包含了影响 MyBatis 行为甚深的设置( settings)和属性( properties)信息。文档的顶层结构如下:

  • configuration 配置
    • properties 属性
    • settings 设置
    • typeAliases 类型命名
    • typeHandlers 类型处理器
    • objectFactory 对象工厂
    • plugins 插件
    • environments 环境
      • environment 环境变量
        • transactionManager 事务管理器
        • dataSource 数据源
    • databaseIdProvider 数据库厂商标识
    • mappers 映射器

注意mybatis全局配置文件中的标签顺序,需要按以下顺序排列,否则抛异常

3.1 引入外部配置文件

<configuration>
	<!--
		1、mybatis可以使用properties来引入外部properties配置文件的内容;
		resource:引入类路径下的资源
		url:引入网络路径或者磁盘路径下的资源
	  -->
	<properties resource ="dbconfig.properties"></properties>
</configuration>

dbconfig.properties

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/learnmybatis?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=CTT
jdbc.username=root
jdbc.password=123

如果属性在不只一个地方进行了配置,那么 MyBatis 将按照下面的顺序来加载:

  • 在 properties 元素体内指定的属性首先被读取。
  • 然后根据 properties 元素中的 resource 属性读取类路径下属性文件或根据 url 属性指定的路径读取属性文件,并覆盖已读取的同名属性。
  • 最后读取作为方法参数传递的属性,并覆盖已读取的同名属性。

3.2 运行时设置(settings)

这是 MyBatis 中极为重要的调整设置,它们会改变MyBatis 的运行时行为。

<configuration>
	...
	
	<!-- 
		2、settings包含很多重要的设置项
		setting:用来设置每一个设置项
			name:设置项名
			value:设置项取值
	 -->
	<settings>
		<!--开启驼峰命名规则-->
		<setting name="mapUnderscoreToCamelCase" value="true"/>
	</settings>

设置参数 描述 有效值 默认值
cacheEnabled 该配置影响的所有映射器中配置的缓存的全局开关。 true/false TRUE
lazyLoadingEnabled 延迟加载的全局开关。当开启时。所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态。 true/false FALSE
useColumnLabel 使用列标签代替列名。不同的驱动在这方面会有不同的表现,具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。 true/false TRUE
defaultStatementTimeout 设置超时时间,它决定驱动等待数据库响应的秒数。 Any positive integer Not Set (null)
mapUnderscoreToCamelCase 是否开启自动驼峰命名规则( camel case )映射即从经典数据库列名A_ COLUMN到经典Java属性名aColumn的类似映射 true/false FALSE

更过设置,可以参考官方文档

3.3 别名配置(typeAliases)

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

<typeAliases>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
  <!--可以指定多个类的别名-->
</typeAliases>

当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。

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

<typeAliases>
  <package name="domain.blog"/>
</typeAliases>

每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子:

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

值得注意的是, MyBatis已经为许多常见的 Java 类型内建了相应的类型别名。它们都是大小写不敏感的,我们在起别名的时候千万不要占用已有的别名。
MyBatis(一)——入门_第1张图片

3.4 类型处理器简介(typeHandlers)

MyBatis 在设置预处理语句(PreparedStatement)中的参数或从结果集中取出一个值时, 都会用类型处理器将获取到的值以合适的方式转换成 Java 类型。
MyBatis(一)——入门_第2张图片
更过类型处理器,参考官方文档

日期类型的处理

日期时间处理上,我们可以使用MyBatis基于JSR310( Date and Time API)编写的各种日期时间类型处理器。MyBatis3.4以前的版本需要我们手动注册这些处理器,以后的版本都是自动注册的。

<typeHandlers>
	<typeHandler handler="org.apache.ibatis.type.InstantTypeHandler" />
	<typeHandler handler="org.apache.ibatis.type.LocalDateTimeTypeHandler" />
	<typeHandler handler="org.apache.ibatis.type.LocalDateTypeHandler" />
	<typeHandler handler="org.apache.ibatis.type.LocalTime TypeHandler" />
	<typeHandler handler="org.apache.ibatis.type.0ffsetDateTimeTypeHandler" />
	<typeHandler handler="org.apache.ibatis.type.OffsetTimeTypeHandler" />
	<typeHandler handler="org.apache.ibatis.type.ZonedDateTimeTypeHandler" />
	<typeHandler handler="org.apache.ibatis.type.YearTypeHandler" />
	<typeHandler handler="org.apache.ibatis.type.MonthTypeHandler" />
	<typeHandler handler="org.apache.ibatis.type.YearMonthTypeHandler" />
	<typeHandler handler="org.apache.ibatis.type.JapaneseDateTypeHandler" />
</typeHandlers>

自定义类型处理器

你可以重写已有的类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型

步骤:

  1. 实现org.apache.ibatis.type.TypeHandler接口或者继承org.apache.ibatis.type.BaseTypeHandler
  2. 指定其映射某个JDBC类型(可选操作)
  3. 在mybatis全局配置文件中注册

3.5 插件简介(plugins)

插件是MyBatis提供的一个非常强大的机制,我们可以通过插件来修改MyBatis的一些核心行为。 插件通过动态代理机制,可以介入四大对象的任何一个方法的执行。

了解mybatis运行原理才能更好开发插件。

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

3.6 运行环境(enviroments)

  • MyBatis可以配置多种环境,比如开发、测试和生产环境需要有不同的配置。
  • 每种环境使用一个environment标签进行配置并指定唯一标识符
  • 可以通过environments标签中的default属性指定一个环境的标识符来快速的切换环境

尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

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

<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </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:指定当前环境的唯一标识
  • transactionManager、和dataSource都必须有

实际开发中,mybatis一般是和spring或者springboot整合,环境不需要单独配置

3.7 多数据库支持(databaseIdProvider)

MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 MyBatis 会加载带有匹配当前数据库 databaseId 属性和所有不带 databaseId 属性的语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。 为支持多厂商特性,只要像下面这样在 mybatis-config.xml 文件中加入 databaseIdProvider 即可:

<databaseIdProvider type="DB_VENDOR" />
  • Type: DB_VENDOR 使用MyBatis提供的VendorDatabaseIdProvider解析数据库厂商标识。也可以实现DatabaseIdProvider接口来自定义。
  • Property-name:数据库厂商标识
  • Property-value:为标识起一个别名,方便SQL语句使用databaseId属性引用

databaseIdProvider 对应的 DB_VENDOR实现会将 databaseId设置为 DatabaseMetaData#getDatabaseProductName()返回的字符串。 由于通常情况下这些字符串都非常长,而且相同产品的不同版本会返回不同的值,你可能想通过设置属性别名来使其变短:

<databaseIdProvider type="DB_VENDOR">
  <property name="SQL Server" value="sqlserver"/>
  <property name="DB2" value="db2"/>
  <property name="Oracle" value="oracle" />
  <property name="MySQL" value="mysql" />
</databaseIdProvider>

databaseId属性在映射xml中的使用

<select id="getEmpById" resultType="com.lun.c01.helloworld.bean.Employee"
	databaseId="mysql">
	select * from employee where id = #{id}
</select>
<select id="getEmpById" resultType="com.lun.c01.helloworld.bean.Employee"
	databaseId="oracle">
	select e.* from employee e where id = #{id}
</select>

通过切换数据库,便能切换SQL

MyBatis匹配规则如下:

  1. 如果没有配置databaseIdProvider标签,那么databaseId=null
  2. 如果配置了databaseIdProvider标签,使用标签配置的name去匹配数据库信息,匹配上设置databaseId=配置指定的值,否则依旧为null
  3. 如果databaseId不为null,他只会找到配置databaseId的sql语句
  4. MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库 databaseId 属性的所有语句。如果同时找到带有 databaseId 和不带 databaseId 的相同语句, 则后者会被舍弃。

3.8 映射器(mappers)

既然 MyBatis 的行为已经由上述元素配置完了,我们现在就要来定义 SQL 映射语句了。 但首先,我们需要告诉 MyBatis 到哪里去找到这些语句。 在自动查找资源方面,Java 并没有提供一个很好的解决方案,所以最好的办法是直接告诉 MyBatis 到哪里去找映射文件。 主要有如下三种方式(都是在标签下的)

  • 加载 resource(resource中可以使用相对路径也可以使用绝对路径)
<mapperresource="mapper/user.xml"/>
  • 使用映射器接口实现类的完全限定类名
<mapper class="com.ewen.mybatis.mapper.UserMapper"/> 

注意

  • 有sql映射文件,映射文件名必须和接口同名,并且放在与接口同一目录下;
  • 没有sql映射文件,所有的sql都是利用注解写在接口上;

推荐:

  • 比较重要的,复杂的Dao接口我们来写sql映射文件
  • 不重要,简单的Dao接口为了开发快速可以使用注解;
  • 将包内的映射器接口实现全部注册为映射器(映射文件名必需与接口文件名称一致)
<packagename ="com.itheima.mybatis.mapper"/>

4. Mybatis Dao的开发方式

4.1 传统方式

创建XXXDao接口,定义抽象方法,创建XXXDaoImpl实现类,这个类中通过sqlSession调用实体类.xml中相应的方法,这种方式,很少用到。

4.2. 接口式编程

上面使用的就是接口式变成,定义一个XXXMapper接口,在XXXMapper.xml中写响应的sql语句,mapper接口没有实现类,但是mybatis会为这个接口生成一个代理对象。(将接口和xml进行绑定)EmployeeMapper empMapper = sqlSession.getMapper(EmployeeMapper.class);,直接使用这个XXXMapper 对象进行操作。

接口和xml文件要满足以下几点要求

  • namespace必需是接口的全路径名
  • 接口的方法名必需与映射文件的sql id一致
  • 接口的输入参数必需与映射文件的parameterType类型一致
  • 接口的返回类型必须与映射文件的resultType类型一致

这是mybatis官方推荐的方式,也是我们会经常用到的一种方式。

5. XML 映射文件

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

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

  • cache – 该命名空间的缓存配置。
  • cache-ref – 引用其它命名空间的缓存配置。
  • resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
  • sql – 可被其它语句引用的可重用语句块。
  • insert – 映射插入语句。
  • update – 映射更新语句。
  • delete – 映射删除语句。
  • select – 映射查询语句。

5.1 增删改查标签中常用的属性

5.1.1 parameterType

将会传入这条语句的参数的类全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。

5.1.2 resultType

期望从这条语句中返回结果的类全限定名或别名。 如果返回的是简单类型或者是pojo类型,直接设置为相应的简单类型或者pojo类型,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。这个属性,只有select标签有。

5.1.3 resultMap

对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。这里只是简单介绍一下,下面还会详细介绍。

5.1.4 获取自增主键的值:

mysql支持自增主键,自增主键值的获取,mybatis也是利用statement.getGenreatedKeys()

  • useGeneratedKeys=“true”;令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。
  • keyProperty;指定对应的主键属性,也就是mybatis获取到主键值以后,将这个值封装给javaBean的哪个属性
	<insert id="addEmp" parameterType="com.lun.c01.helloworld.bean.Employee"
		useGeneratedKeys="true" keyProperty="id" >
		insert into employee(last_name,email,gender) 
		values(#{lastName},#{email},#{gender})
	</insert>

上面两个属性,仅适用于仅适用于 insert 和 update标签

对于不支持自动生成主键列的数据库和可能不支持自动生成主键的 JDBC 驱动,MyBatis 有另外一种方法来生成主键

Oracle不支持自增,Oracle使用序列来模拟自增,每次插入的数据的主键是从序列中拿到的值,如何获取到这个值

#从序列获取新主键值
select employee_seq.nextval from dual;

获取非自增主键的值

<insert id="addEmp" databaseId="oracle">
	<!-- 
	keyProperty:查出的主键值封装给javaBean的哪个属性
	order="BEFORE":当前sql在插入sql之前运行
		   AFTER:当前sql在插入sql之后运行
	resultType:查出的数据的返回值类型
	
	BEFORE运行顺序:
		先运行selectKey查询id的sql;查出id值封装给javaBean的id属性
		在运行插入的sql;就可以取出id属性对应的值
	AFTER运行顺序:
		先运行插入的sql(从序列中取出新值作为id);
		再运行selectKey查询id的sql-->
	<selectKey keyProperty="id" order="BEFORE" resultType="Integer">
		<!-- 编写查询主键的sql语句 -->
		<!-- BEFORE-->
		select EMPLOYEES_SEQ.nextval from dual 
		<!-- AFTER:
		 select EMPLOYEES_SEQ.currval from dual -->
	</selectKey>
	
	<!-- 插入时的主键是从序列中拿到的 -->
	<!-- BEFORE:-->
	insert into employees(EMPLOYEE_ID,LAST_NAME,EMAIL) 
	values(#{id},#{lastName},#{email}) 

	<!-- AFTER:
	insert into employees(EMPLOYEE_ID,LAST_NAME,EMAIL) 
	values(employees_seq.nextval,#{lastName},#{email}) -->
</insert>

在上面的示例中,首先会运行 selectKey 元素中的语句,并设置 employees的 id,然后才会调用插入语句。这样就实现了数据库自动生成主键类似的行为,同时保持了 Java 代码的简洁。

5.2 参数

  • 单个参数:mybatis不会做特殊处理

#{参数名/任意名}:取出参数值。

  • 多个参数:mybatis会做特殊处理,被封装成 一个map,

通常操作:
方法:public Employee getEmpByIdAndLastName(Integer id,String lastName);
如果使用#{id},#{lastName},会抛出异常:org.apache.ibatis.binding.BindingException: Parameter 'id' not found. Available parameters are [1, 0, param1, param2]
要使用#{param1}、#{param2}…从map中获取指定的key的值,也可以使用索引如#{0}、#{1}

多个参数,使用param1、param2…,程序可读性不高,可以使用@Param来命名参数

  • 【命名参数】:明确指定封装参数时map的key;@Param(“id”)

方法:public Employee getEmpByIdAndLastName4(@Param("id")Integer id, @Param("lastName")String name);
多个参数会被封装成 一个map,
key:使用@Param注解指定的值
value:参数值
#{指定的key}取出对应的参数值

  • POJO

如果多个参数正好是我们业务逻辑的数据模型,我们就可以直接传入pojo;
#{属性名}:取出传入的pojo的属性值

  • Map

如果多个参数不是业务模型中的数据,没有对应的pojo,不经常使用,为了方便,我们也可以传入map
#{key}:取出map中对应的值

  • TO

如果多个参数不是业务模型中的数据,但是经常要使用,推荐来编写一个TO(Transfer Object)数据传输对象,如:
Page{ int index; int size; }

举例
public Employee getEmp(@Param("id")Integer id,String lastName);

  • 取值:id==>#{id/param1} lastName==>#{param2}

public Employee getEmp(Integer id,@Param("e")Employee emp);

  • 取值:id==>#{param1} lastName===>#{param2.lastName/e.lastName}

注意: 如果是Collection(List、Set)类型或者是数组,也会特殊处理。也是把传入的list或者数组封装在map中。
key:Collection(collection),如果是List还可以使用这个key(list),数组key(array)
比如:public Employee getEmpById(List ids); 取值:取出第一个id的值: #{list[0]}

5.3 参数处理源码分析

在idea中,debug
MyBatis(一)——入门_第3张图片
点进selectById(1)方法

MyBatis(一)——入门_第4张图片
mapperMethod.execute()方法中,会判断是那种类型的sql语句,并进行参数转换,之后,还是用sqlSession中的相应方法操作

MyBatis(一)——入门_第5张图片
点进convertArgsToSqlCommandParam方法

在这里插入图片描述
点进getNamedParams

MyBatis(一)——入门_第6张图片
这个names是怎么获得的呢?

namesParamNameResolver类的一个属性

 private final SortedMap<Integer, String> names;

并且在构造方法中进行的赋值

 public ParamNameResolver(Configuration config, Method method) {
        Class<?>[] paramTypes = method.getParameterTypes();
        Annotation[][] paramAnnotations = method.getParameterAnnotations();
        SortedMap<Integer, String> map = new TreeMap();
        int paramCount = paramAnnotations.length;

        for(int paramIndex = 0; paramIndex < paramCount; ++paramIndex) {
            if (!isSpecialParameter(paramTypes[paramIndex])) {
                String name = null;
                Annotation[] arr$ = paramAnnotations[paramIndex];
                int len$ = arr$.length;
				//判断是否有@Param注解
                for(int i$ = 0; i$ < len$; ++i$) {
                    Annotation annotation = arr$[i$];
                    if (annotation instanceof Param) {
                        this.hasParamAnnotation = true;
                        //有的话,获取@param中的值
                        name = ((Param)annotation).value();
                        break;
                    }
                }

                if (name == null) {
                    if (config.isUseActualParamName()) {
                        name = this.getActualParamName(method, paramIndex);
                    }
					//如果没有注解的话,会获取参数的个数
                    if (name == null) {
                        name = String.valueOf(map.size());
                    }
                }
				//存到一个map中,paramIndex 是索引从0开始,如果有注解,name是注解中的值,没有的话,name就是索引
                map.put(paramIndex, name);
            }
        }
        //最后存到names中
        this.names = Collections.unmodifiableSortedMap(map);
    }

下面再来分析getNamedParams方法

 public Object getNamedParams(Object[] args) {
        int paramCount = this.names.size();
        
        if (args != null && paramCount != 0) { //参数不为空
        	//如果只有一个元素,并且没有注解,直接返回单个参数
            if (!this.hasParamAnnotation && paramCount == 1) { 
                return args[(Integer)this.names.firstKey()];
            } else {
            	//如果有多个元素或者有@param注解
                Map<String, Object> param = new ParamMap();
                int i = 0;
				//遍历names集合
                for(Iterator i$ = this.names.entrySet().iterator(); i$.hasNext(); ++i) {
                    Entry<Integer, String> entry = (Entry)i$.next();
                    //names集合的value作为key;  names集合的key又作为从参数args取值的索引args[0]:args【1,"Tom"】:
      				//names既可以是@Param中的值(指定的key),也可以是索引
                    param.put(entry.getValue(), args[(Integer)entry.getKey()]);
                    //额外的将每一个参数也保存到map中,使用新的key:param1...paramN
                    //效果:有Param注解可以#{指定的key},或者#{param1}
                    String genericParamName = "param" + String.valueOf(i + 1);
                    if (!this.names.containsValue(genericParamName)) {
                        param.put(genericParamName, args[(Integer)entry.getKey()]);
                    }
                }

                return param;
            }
        } else {
        	//如果参数为空,直接返回null
            return null;
        }
    }

5.4 获取参数的值

#{}${}都可以获取map中的值或者pojo对象属性的值;

select * from tbl_employee where id=${id} and last_name=#{lastName}
#Preparing:
select * from tbl_employee where id=2 and last_name=?

区别:

  • #{} : 是以预编译的形式,将参数设置到sql语句中;PreparedStatement;防止sql注入
  • ${} : 取出的值直接拼装在sql语句中;会有安全问题;

默认情况下,使用 #{} 参数语法时,MyBatis 会创建 PreparedStatement参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)。 这样做更安全,更迅速,通常也是首选做法。

原生jdbc不支持占位符的地方我们就可以使用${}进行取值,比如分表、排序…,按照年份分表拆分,以及直接在 SQL 语句中直接插入一个不转义的字符串(比如模糊查询)的时候,要使用${}

select * from ${year}_salary where xxx;
select * from tbl_employee order by ${f_name} ${order}

另外,使用#{}取值时,可以规定参数的一些规则

  • javaType、
  • jdbcType、
  • mode(存储过程)、
  • numericScale、
  • resultMap、
  • typeHandler、
  • jdbcTypeName、
  • expression(未来准备支持的功能);

jdbcType通常需要在某种特定的条件下被设置:

在我们数据为null的时候,有些数据库可能不能识别mybatis对null的默认处理。比如Oracle DB(报错) `JdbcType OTHER:无效的类型

因为mybatis对所有的null都映射的是原生JdbcOTHER类型,Oracle DB不能正确处理;

由于全局配置中:jdbcTypeForNull=OTHER,Oracle DB不支持,两种解决方法:

  • 在mapper文件中写#{email,jdbcType=NULL}
  • vv在全局配置文件

5.5 结果映射

使用resultType属性指定返回结果的类型,如果返回结果是简单类型、Pojo类型,直接在resultType中指定即可。

如果返回结果是List类型,resultType中直接指定为list中的Pojo类

  • 如果是返回map,map中存多条记录,resultTypevalue属性
//多条记录封装一个map:Map:键是这条记录的主键,值是记录封装后的javaBean
//@MapKey:告诉mybatis封装这个map的时候使用哪个属性作为map的key
@MapKey("lastName")
public Map<String, Employee> getEmpByLastNameLikeReturnMap(String lastName);
<!--public Map<Integer, Employee> getEmpByLastNameLikeReturnMap(String lastName);  -->
 	<select id="getEmpByLastNameLikeReturnMap" resultType="employee">
 		select * from employee where last_name like #{lastName}
 	</select>
  • 如果返回一条记录,resultTypemap
//返回一条记录的map;key就是列名,值就是对应的值
public Map<String, Object> getEmpByIdReturnMap(Integer id);
<!--public Map<String, Object> getEmpByIdReturnMap(Integer id);  -->
 	<select id="getEmpByIdReturnMap" resultType="map">
 		select * from employee where id=#{id}
 	</select>

下面来介绍一下resultMap

resultMap 元素是 MyBatis中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的数千行代码。ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。

之前你已经见过简单映射语句的示例,它们没有显式指定 resultMap。比如:

<select id="selectById" parameterType="int" resultType="User"> 
	select * from `user` where id=#{id}
</select>

如果,数据库中的字段名和实体类中的列名不一样的时候(或者根据驼峰命名法也不能匹配),可以使用标签

<resultMap id="userResultMap" type="User">
<!--指定主键列的封装规则
		id定义主键会底层有优化;
		column:指定哪一列
		property:指定对应的javaBean属性 -->
  <id property="id" column="user_id" />
  <result property="username" column="user_name"/>
  <result property="password" column="hashed_password"/>
</resultMap>

然后在引用它的语句中设置 resultMap 属性就行了(注意我们去掉了 resultType 属性)。比如:

<select id="selectUsers" resultMap="userResultMap">
  select user_id, user_name, hashed_password
  from some_table
  where id = #{id}
</select>

的高级用法,可以参考我的另一篇文章

参考:

  • myabtis官方文档
  • 尚硅谷mybatis教程

如有不足之处,欢迎指正,谢谢

你可能感兴趣的:(mybatis,mybatis,学习笔记)