MyBatis
1. 框架的作用
MyBatis是持久层框架。
在Java实现数据库编程时,主要通过JDBC来实现,而JDBC相关代码在实现过程中流程相对固定,不论哪种数据操作,代码方面差距不大,所以,就出现了各种减化开发的持久层框架,常见的有Hibernate和MyBatis。
使用MyBatis框架时,无需编写JDBC相关代码,只需要为某些抽象方法配置其对应的需要执行的SQL语句即可。
2.3. 创建Maven项目
创建项目时必须做的5件事情(添加web.xml,添加Tomcat环境,添加依赖,复制web.xml中的DispatcherServlet和CharacterEncodingFilter的配置,复制spring-mvc.xml)。
完成后,打开spring-mvc.xml
,删除与本次项目不相关的配置,例如拦截器的配置等等。
在使用MyBatis时,还需要添加相关依赖!首先,在pom.xml中,将spring-webmvc的依赖复制一份,修改为spring-jdbc:
org.springframework
spring-jdbc
4.3.10.RELEASE
在同一个项目中,如果需要使用多个以
spring-
作为前缀的依赖,则应该使用相同的版本。
然后,还应该检查是否已经添加mysql-connector-java
依赖,用于操作mysql数据库:
mysql
mysql-connector-java
5.1.6
继续检查是否已经添加commons-dbcp
依赖,用于处理数据库连接池等:
commons-dbcp
commons-dbcp
1.4
继续检查是否已经添加junit
依赖,用于单元测试:
junit
junit
4.12
然后,添加mybatis
依赖:
org.mybatis
mybatis
3.5.0
通常,会将mybatis与spring整合起来使用,添加mysql-spring
依赖:
org.mybatis
mybatis-spring
2.0.0
注意:后续代码运行时,如果本应该存在的类/接口却提示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
中:
<!-- 读取db.properties中的配置 -->
<util:properties id="db" location="classpath:db.properties" />
<!-- 配置数据源 -->
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="url" value="#{db.url}" />
<property name="driverClassName" value="#{db.driver}" />
<property name="username" value="#{db.username}" />
<property name="password" value="#{db.password}" />
<property name="initialSize" value="#{db.initialSize}" />
<property name="maxActive" value="#{db.maxActive}" />
</bean>
2.5. 接口与抽象方法
创建cn.tedu.entity.User实体
类,在类中声明与数据表对应的属性相对应:
每张数据表都应该有一个与之对应的实体类。
创建cn.tedu.mybatis.UserMapper
接口,并在接口中声明抽象方法:
Integer addnew(User user);
在使用MyBatis时,如果抽象方法对应需要执行的数据操作是增、删、改,则抽象方法的返回值应该是Integer
,表示受影响的行数;抽象方法的名称可以自由定义,符合语法规范并能表达语义即可;抽象方法的参数应该根据执行SQL语句时所需要的参数来决定。
然后,需要在Spring的配置文件中配置MapperScannerConfigurer
,用于扫描接口文件:
<property name="basePackage"value=“cn.tedu.mybatis” />
2.6. 配置映射
在resources
下创建名为mappers
的文件夹,关于UserMapper.xml
的配置:
<mapper namespace="cn.tedu.mybatis.UserMapper">
<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>
、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
**
1. 多参数的处理
MyBatis框架只识别抽象方法中的1个参数,即:抽象方法应该最多只有1个参数!
当某个抽象方法的参数只有1个时,例如:
Integer deleteById(Integer uid);
在配置映射时,占位符中的名称可以是随意的名字,例如:
DELETE FROM t_user WHERE id=#{helloworld}
因为参数只有1个,框架会自动的读取对应位置的参数并应用于执行SQL语句,所以,参数的名称根本就不重要!
另外,每个.java
源文件在被编译成为.class
文件之后,也都会丢失参数名称!
可以将抽象方法的参数设计为1个Map,则可以满足“参数只有1个”的需求,但是,使用时非常不方便,因为方法的调用者并不明确向Map中封装数据时应该使用什么名称的key!
为了解决这个问题,MyBatis中提供了@Param
注解,在设计抽象方法时,允许使用多个参数,但是,每个参数之前都应该添加该注解,并在注解中设置参数的名称,后续在执行时,MyBatis会基于这些注解将调用时的参数封装为Map来执行:
![](01.png)
【小结】 当需要多个参数时(超过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 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>
对应关系是:
![](03.png)
4. MyBatis中的动态SQL
4.1. 基本概念
在配置映射时,可以添加例如<if>
、<foreach>
等节点,可以根据参数的不同,从而生成不同的SQL语句。
4.2. 关于
例如有某个需求:批量删除用户的数据,需要执行的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>
标签可以实现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. 关于
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>