## MyBatis持久层框架,实现数据访问,抽象方法多参数的处理,关于别名,MyBatis中的动态SQL,resultMap

### 1. 框架的作用

MyBatis是持久层框架。

在Java实现数据库编程时,主要通过JDBC来实现,而JDBC相关代码在实现过程中流程相对固定,不论哪种数据操作,代码方面差距不大,所以,就出现了各种减化开发的持久层框架,常见的有Hibernate和MyBatis。

使用MyBatis框架时,无需编写JDBC相关代码,只需要为某些抽象方法配置其对应的需要执行的SQL语句即可。

### 2. 使用MyBatis实现数据访问

#### 2.1. 案例目标:通过MyBatis实现用户注册

假设用户数据有:用户名、密码、年龄、手机号码、电子邮箱,通过使用MyBatis框架,实现向数据库的表中插入用户数据。

#### 2.2. 创建数据库与数据表

创建数据库:

CREATE DATABASE tedu_ums;

使用数据库:

	USE tedu_ums;

创建数据表:

CREATE TABLE t_user (
		id INT AUTO_INCREMENT,
		username VARCHAR(20) UNIQUE NOT NULL,
		password VARCHAR(20) NOT NULL,
		age INT,
		phone VARCHAR(20),
		email VARCHAR(20),
		PRIMARY KEY(id)
	) DEFAULT CHARSET=UTF8;

#### 2.3. 创建项目

创建项目时必须做的5件事情(添加web.xml,添加Tomcat环境,添加依赖,复制web.xml中的DispatcherServlet和CharacterEncodingFilter的配置,复制spring-mvc.xml)。

完成后,打开`spring-mvc.xml`,删除与本次项目不相关的配置,例如拦截器的配置等等。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context" 
	xmlns:jdbc="http://www.springframework.org/schema/jdbc"  
	xmlns:jee="http://www.springframework.org/schema/jee" 
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:aop="http://www.springframework.org/schema/aop" 
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:util="http://www.springframework.org/schema/util"
	xmlns:jpa="http://www.springframework.org/schema/data/jpa"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
		http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd
		http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
		http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
		http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd		
		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd">
		
	<bean id="resourceView" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/"></property>
		<property name="suffix" value=".jsp"></property>
	</bean>
	
</beans>

在使用MyBatis时,还需要添加相关依赖!首先,在pom.xml中,将spring-webmvc的依赖复制一份,修改为spring-jdbc:

<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-jdbc</artifactId>
		<version>4.3.10.RELEASE</version>
	</dependency>

 在同一个项目中,如果需要使用多个以`spring-`作为前缀的依赖,则应该使用相同的版本。

然后,还应该检查是否已经添加`mysql-connector-java`依赖,用于操作mysql数据库:

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

继续检查是否已经添加`commons-dbcp`依赖,用于处理数据库连接池等:

<dependency>
		<groupId>commons-dbcp</groupId>
		<artifactId>commons-dbcp</artifactId>
		<version>1.4</version>
	</dependency>

继续检查是否已经添加`junit`依赖,用于单元测试:

<dependency>
		<groupId>junit</groupId>
		<artifactId>junit</artifactId>
		<version>4.12</version>
	</dependency>


然后,添加`mybatis`依赖:

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

通常,会将mybatis与spring整合起来使用,添加`mysql-spring`依赖:

<dependency>
		<groupId>org.mybatis</groupId>
		<artifactId>mybatis-spring</artifactId>
		<version>2.0.0</version>
	</dependency>

**注意:后续代码运行时,如果本应该存在的类/接口却提示ClassNotFoundException,或提示ZipException,则意味着新添加的依赖是下载失败的,需要删除对应的jar文件甚至所在的文件夹,然后重新更新项目。**

#### 2.4. 建立连接

在`resources`下创建`db.properties`文件,用于配置数据库连接:

url=jdbc:mysql://localhost:3306/tedu_ums?useUnicode=true&characterEncoding=utf-8
	driver=com.mysql.jdbc.Driver
	username=root
	password=root
	initialSize=2
	maxActive=10

将`spring-mvc.xml`复制,并粘贴为`spring-dao.xml`,删除文件中原有的配置,并加载`db.properties`,将配置的属性注入到`BasicDataSource`中:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context" 
	xmlns:jdbc="http://www.springframework.org/schema/jdbc"  
	xmlns:jee="http://www.springframework.org/schema/jee" 
	xmlns:tx="http://www.springframework.org/schema/tx"
	xmlns:aop="http://www.springframework.org/schema/aop" 
	xmlns:mvc="http://www.springframework.org/schema/mvc"
	xmlns:util="http://www.springframework.org/schema/util"
	xmlns:jpa="http://www.springframework.org/schema/data/jpa"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
		http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.2.xsd
		http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd
		http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd
		http://www.springframework.org/schema/data/jpa http://www.springframework.org/schema/data/jpa/spring-jpa-1.3.xsd
		http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd
		http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd		
		http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.2.xsd">
	<!-- 读取db.properties中的配置 -->	
	<util:properties id="db" location="classpath:db.properties"></util:properties>
	<!-- 配置数据源 -->
	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
		<property name="url" value="#{db.url}"></property>
		<property name="driverClassName" value="#{db.driver}"></property>
		<property name="username" value="#{db.username}"></property>
		<property name="password" value="#{db.password}"></property>
		<property name="initialSize" value="#{db.initialSize}"></property>
		<property name="maxActive" value="#{db.maxActive}"></property>
	</bean>
	<!-- MapperScannerConfigurer -->
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
	<!-- 指定接口文件所在的(根!!!)包 -->
		<property name="basePackage" value="cn.tedu.mybatis"></property>
	</bean>
	<bean class="org.mybatis.spring.SqlSessionFactoryBean">
	<!-- 映射的XML文件的位置 -->
	<property name="mapperLocations" value="classpath:mappers/*.xml"></property>
	<!-- 指定数据源,取值将引用(ref)以上配置的数据源的 id-->
	<property name="dataSource" ref="dataSource"></property>
	</bean>
</beans>

然后,编写并执行单元测试:

@Test
	public void getConnection() throws SQLException {
		// 加载Spring配置文件,获取Spring容器
		AbstractApplicationContext ac
			= new ClassPathXmlApplicationContext(
					"spring-dao.xml");
		
		// 从Spring容器中获取对象
		BasicDataSource dataSource 
			= ac.getBean("dataSource", BasicDataSource.class);
		
		// 测试
		Connection conn = dataSource.getConnection();
		System.out.println(conn);
		
		// 释放资源
		ac.close();
	}

#### 2.5. 接口与抽象方法

创建`cn.tedu.mybatis.User`类,在类中声明与数据表对应的6个属性:

public class User {

		private Integer id;
		private String username;
		private String password;
		private Integer age;
		private String phone;
		private String email;

		// SET/GET/toString

	}

每张数据表都应该有一个与之对应的实体类。

创建`cn.tedu.mybatis.UserMapper`接口,并在接口中声明抽象方法:

    Integer addnew(User user);
    
> 在使用MyBatis时,如果抽象方法对应需要执行的数据操作是增、删、改,则抽象方法的返回值应该是`Integer`,表示受影响的行数;抽象方法的名称可以自由定义,符合语法规范并能表达语义即可;抽象方法的参数应该根据执行SQL语句时所需要的参数来决定。

然后,需要在Spring的配置文件中配置`MapperScannerConfigurer`,用于扫描接口文件:

<!-- MapperScannerConfigurer -->
	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<!-- 指定接口文件所在的根包 -->
		<property name="basePackage"
			value="cn.tedu.mybatis" />
	</bean>

#### 2.6. 配置映射

从FTP下载`somemapper.zip`并解压得到`SomeMapper.xml`文件。

在`resources`下创建名为`mappers`的文件夹,将`SomeMapper.xml`复制到该文件夹下,并修改文件名为`UserMapper.xml`。

关于`UserMapper.xml`的配置:

<!-- namespace:匹配的Java接口 -->
	<mapper namespace="cn.tedu.mybatis.UserMapper">
	
		<!-- 根据将要执行的SQL语句的类型选择节点 -->
		<!-- id:对应的抽象方法的方法名 -->
		<!-- 在SQL语句中的?应该使用#{}来占位 -->
		<!-- 在#{}中的名称是参数User类中的属性名 -->
		<insert id="addnew">
			INSERT INTO t_user (
				username, password,
				age, phone,
				email
			) VALUES (
				#{username}, #{password},
				#{age}, #{phone},
				#{email}
			)
		</insert>
	
	</mapper>

为了使得MyBatis框架能找到这些XML的映射文件并执行其中的SQL语句,还需要在Spring的配置文件中配置:

<!-- SqlSessionFactoryBean -->
	<bean class="org.mybatis.spring.SqlSessionFactoryBean">
		<!-- 映射的XML文件的位置 -->
		<property name="mapperLocations"
			value="classpath:mappers/*.xml" />
		<!-- 指定数据源,取值将引用(ref)以上配置的数据源的id -->
		<property name="dataSource"
			ref="dataSource" />
	</bean>


至此,开发任务已经完成,可以编写并执行单元测试:

@Test
	public void addnew() {
		AbstractApplicationContext ac
			= new ClassPathXmlApplicationContext(
				"spring-dao.xml");
		
		UserMapper userMapper
			= ac.getBean("userMapper", UserMapper.class);
		System.out.println(userMapper);
		
		User user = new User();
		user.setUsername("root");
		user.setPassword("1234");
		user.setAge(18);
		user.setPhone("13800138001");
		user.setEmail("root@tedu.cn");
		Integer rows = userMapper.addnew(user);
		System.out.println("rows=" + rows);
		
		ac.close();
	}

### 1. 多参数的处理

MyBatis框架只识别抽象方法中的1个参数,即:抽象方法应该最多只有1个参数

当某个抽象方法的参数只有1个时,例如:

    Integer deleteById(Integer uid);

在配置映射时,占位符中的名称可以是随意的名字,例如:

    <delete id="deleteById">
        DELETE FROM t_user WHERE id=#{helloworld}
    </delete>

因为参数只有1个,框架会自动的读取对应位置的参数并应用于执行SQL语句,所以,参数的名称根本就不重要!

另外,每个`.java`源文件在被编译成为`.class`文件之后,也都会丢失参数名称!

可以将抽象方法的参数设计为1个Map,则可以满足“参数只有1个”的需求,但是,使用时非常不方便,因为方法的调用者并不明确向Map中封装数据时应该使用什么名称的key!

为了解决这个问题,MyBatis中提供了`@Param`注解,在设计抽象方法时,允许使用多个参数,但是,每个参数之前都应该添加该注解,并在注解中设置参数的名称,后续在执行时,MyBatis会基于这些注解将调用时的参数封装为Map来执行

【小结】 当需要多个参数时(超过1个参数时),每个参数前都添加`@Param`注解,并且,注解中的配置的名称、方法的参数名称、XML中占位符中的名称应该都使用相同的名称!

### 2. 查询数据

与增、删、改相差不大,查询时也应该先设计抽象方法,再配置映射。

在配置抽象方法时,返回值的类型应该根据查询需求来决定,即:调用该方法后希望得到什么的数据。

例如:**根据用户的id查询用户详情**,则对应的抽象方法应该是:

    User findById(Integer id);

在配置映射时,查询所使用的`<select>`必须配置`resultType`属性或`resultMap`属性(二选一)
 

<select id="findById"
		resultType="cn.tedu.mybatis.User">
		SELECT 
			id, username,
			password, age,
			phone, email
		FROM 
			t_user 
		WHERE 
			id=#{id}
	</select>

例如:**查询当前表中所有的数据**:

List<User> findAll();

在配置映射时,使用的`resultType`值依然是`User`的类型,因为,其实所有的查询得到的都可以是一系列的数据,所以,查询多条数据时,无需告诉MyBatis结果将是`List`集合,只需要告诉它`List`集合内部的元素是哪种类型就可以了:

<select id="findAll"
		resultType="cn.tedu.mybatis.User">
		SELECT 
			id, username,
			password, age,
			phone, email
		FROM 
			t_user 
	</select>

例如:**获取当前表中的数据的数量**:

Integer countById();

在配置映射时,需要注意:无论多么简单的返回值类型,都必须配置`resultType`!

<select id="countById"
		resultType="java.lang.Integer">
		SELECT COUNT(id) FROM t_user
	</select>

### 3. 关于别名

在数据表中添加`is_delete INT`字段,用于表示**该数据是否标记为删除**:

    ALTER TABLE t_user ADD COLUMN is_delete INT;

执行以上代码后,数据表中会出现新的`is_delete`字段,原有的各数据的该字段值均为`NULL`,再执行:

    UPDATE t_user SET is_delete=0;

则可以把所有数据的`is_delete`都设为`0`。

当数据表的结构发生变化时,实体类`cn.tedu.mybatis.User`也应该一并调整:

public class User {

		private Integer id;
		private String username;
		private String password;
		private Integer age;
		private String phone;
		private String email;
		private Integer isDelete;

		// SET/GET/toString
	}

在增加时,需要注意填写字段名与属性名:

<insert id="addnew">
		INSERT INTO t_user (
			username, password,
			age, phone,
			email, is_delete
		) VALUES (
			#{username}, #{password},
			#{age}, #{phone},
			#{email}, #{isDelete}
		)
	</insert>

对应关系应该是:

在查询操作中,需要添加新的字段,并定义别名:

  

   <select id="findAll"
        resultType="cn.tedu.mybatis.User">
        SELECT 
            id, username,
            password, age,
            phone, email,
            is_delete AS isDelete
        FROM 
            t_user 
    </select>

对应关系是:

### 4. MyBatis中的动态SQL

#### 4.1. 基本概念

在配置映射时,可以添加例如`<if>`、`<foreach>`等节点,可以根据参数的不同,从而生成不同的SQL语句。

#### 4.2. 关于<foreach>

例如有某个需求:批量删除用户的数据,需要执行的SQL语句例如:

    DELETE FROM t_user WHERE id=3 OR id=4 OR id=5;

通常,更推荐使用`IN`关键字:

    DELETE FROM t_user WHERE id IN (3,4,5)

以上SQL语句中,`IN`右侧的括号中的id的数量是未知的,可能由用户的操作来决定!这就需要使用动态SQL来实现。

在MyBatis的应用中,如果需要实现以上功能,首先,还是先设计抽象方法:

  

    Integer deleteByIds(Integer[] ids);

> 在以上抽象方法中,参数既可以使用数组,也可以使用`List`集合来表示。

配置映射例如:
 

<delete id="deleteByIds">
		DELETE FROM 
			t_user
		WHERE 
			id IN (
			<foreach collection="array"
				item="id" separator=",">
				#{id}
			</foreach>
			)
	</delete>

关于`<foreach>`节点中各属性的配置:

- `collection`:被遍历的集合,当对应的抽象方法只有1个参数时,取值将根据参数的类型来选择`array`或`list`;当对应的抽象方法有多个参数时,取值应该是`@Param`注解中的名称!

- `item`:遍历过程中元素的名称;

- `seperator`:分隔符,例如在`IN`语句中,分隔符应该是逗号`,`;

- `open`和`close`:遍历生成的代码区域左侧与右侧的值,例如:当没有在`<foreach>`外部使用括号框住时,可以配置`open="("`和`close=")"`。

#### 4.3. 关于<if>

使用`<if>`标签可以实现SQL语句中的判断,例如当某参数有值或没有值时,如何执行后续的SQL语句:

<select id="findUserList"
		resultType="cn.tedu.mybatis.User">
		SELECT 
			*
		FROM 
			t_user 
		<if test="where != null">
		WHERE 
			${where}
		</if>
		<if test="orderBy != null">
		ORDER BY 
			${orderBy} 
		</if>
		<if test="offset != null">
		LIMIT 
			#{offset}, #{count}
		</if>
	</select>

**以上代码几乎可以实现任何的单表且不包含聚合函数的查询,但是,并不推荐在实际开发中这样使用,会造成效率低下、浪费内存资源等相关问题。**

#### 4.4. 关于#{}和${}占位符

在MyBatis中支持`#{}`和`${}`占位符。

在SQL语句中,可以填写`?`实现预编译的位置,需要使用`#{}`占位符,例如:

    DELETE FROM t_user WHERE id=?

而不可以写`?`的位置,其内容也表现为SQL语句中的部分语法需要使用`${}`占位符。

使用`#{}`在执行时是预编译的,而`${}`在执行时只是单纯的通过字符串拼接形成最终的SQL语句,则可能存在SQL注入的风险!

### 5. 关于<resultMap>

#### 5.1. 使用需求

在设计查询时,`<select>`节点必须配置`resultType`或`resultMap`其中的一项!

通常,只有在多表查询时,才需要使用`resultMap`。

#### 5.2. 准备工作

创建新的数据表,表示**部门信息表**:


	CREATE TABLE t_department (
		did INT AUTO_INCREMENT,
		name VARCHAR(20) NOT NULL,
		PRIMARY KEY (did)
	) DEFAULT CHARSET=UTF8;

然后,应该创建与之对应的实体类`cn.tedu.mybatis.Department`:

public class Department {

		private Integer did;
		private String name;

		// SET/GET/toString

	}

由于**部门信息表**应该与**用户数据表**存在关联,则需要在**用户信息表**中添加新的字段,用于表示每个用户所归属的部门:

ALTER TABLE t_user ADD COLUMN did INT;

最后,完善一下测试数据:

INSERT INTO t_department (name) VALUES ('软件研发部'), ('市场部'), ('人力资源部');

以及**用户数据表**中的数据:

    UPDATE t_user SET did=3 WHERE id=4;
    UPDATE t_user SET did=1 WHERE id=5;
    UPDATE t_user SET did=1 WHERE id=9;

假设存在需求:**显示某用户的详情,其中,部门应该显示部门的名称,而不是部门的id值**,则SQL语句应该是:

SELECT 
        id, username, password, age, phone, email, name
    FROM 
        t_user
    LEFT JOIN 
        t_department
    ON 
        t_user.did=t_department.did
    WHERE 
        t_user.id=5

在使用MyBatis时,首先,应该设计抽象方法:

    ?? findUserById(Integer id);

可以发现,并没有某个实体类能够存储以上查询结果,在处理这种关联查询时,应该在项目中创建对应的VO类(Value Object),通常,VO类都是与实际的查询需求相对应的:

public class UserVO {

		private Integer id;
		private String username;
		private String password;
		private Integer age;
		private String phone;
		private String email;
		private String departmentName;

		// SET/GET/toString

	}

所以,关于这个查询功能对应的抽象方法应该是:

    UserVO findUserById(Integer id);

> 实体类与VO类从类的结构上来说,是高度相似的,区别在于:实体类是与数据表结构相对应的,VO类是与查询结果和实际使用相对应的。

然后,配置映射:

<select id="findUserById"
		resultType="cn.tedu.mybatis.UserVO">
		SELECT 
			id, username, 
			password, age, 
			phone, email, 
			name AS departmentName
		FROM 
			t_user
		LEFT JOIN 
			t_department
		ON 
			t_user.did=t_department.did
		WHERE 
			t_user.id=#{id}
	</select>

假设存在需求:**显示某部门的详情,其中,应该包括该部门的所有员工**,则SQL语句应该是:

    SELECT 
        id, username, password, age, phone, email, 
        name AS departmentName
    FROM 
        t_department
    LEFT JOIN 
        t_user
    ON 
        t_user.did=t_department.did
    WHERE 
        t_department.did=1

如果部门id为1的有多个用户,在执行SQL查询后,将得到多条查询结果,但是,需求本身是**显示某部门的详情**,应该只有1个查询结果!

首先,还是应该先创建对应的VO类`cn.tedu.mybatis.DepartmentVO`:

public class DepartmentVO {

		private Integer did;
		private String name;
		private List<User> users; // 表示部门中的若干员工

		// SET/GET/toString

	}

则查询时的抽象方法应该是:

    DepartmentVO findDepartmentById(Integer did);

配置映射:

<!-- 当前resultMap用于指导mybatis将多条查询结果封装到同1个对象中 -->
	<resultMap id="DepartmentVOMap"
		type="cn.tedu.mybatis.DepartmentVO">
		<!-- id节点:用于配置主键 -->
		<!-- column属性:查询结果中的列名 -->
		<!-- property属性:resultMap中type对应的数据类型中的名称 -->
		<id column="did" property="did" />
		<!-- result节点:用于配置主键以外的其它字段的查询 -->
		<result column="name" property="name" />
		<!-- collection节点:用于配置1对多关系的数据,也就是List类型的属性 -->
		<!-- ofType属性:List集合中的元素类型 -->
		<collection property="users" ofType="cn.tedu.mybatis.User">
			<id column="id" property="id" />
			<result column="username" property="username" />
			<result column="password" property="password" />
			<result column="age" property="age" />
			<result column="phone" property="phone" />
			<result column="email" property="email" />
		</collection>
	</resultMap>

	<select id="findDepartmentById"
		resultMap="DepartmentVOMap">
		SELECT 
			did, id, username, password, age, phone, email, name
		FROM 
			t_department
		LEFT JOIN 
			t_user
		ON 
			t_user.did=t_department.did
		WHERE 
			t_department.did=#{did}
	</select>

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值