Mybatis 基础
Mybatis 基础
Mybatis 基础笔记
作用
简化持久层开发,当需要开发某个增删改查功能时,程序员只需要定义好该功能对应的抽象方法,及该抽象方法的功能对应的SQL语句即可。
创建
与创建SpringMVC项目的步骤相同,另外,增加添加依赖:
<dependencys>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<!-- MyBatis整合Spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.1</version>
</dependency>
<!-- Spring-JDBC,与spring-webmvc使用相同版本 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.8.RELEASE</version>
</dependency>
<!-- MySQL连接 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
</dependencys>
配置数据库连接
在src/main/resources下创建db.properties
文件,用于配置数据库连接:
url=jdbc:mysql://localhost:3306/tedu_ums?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
driver=com.mysql.cj.jdbc.Driver
username=root
password=root
initialSize=2
maxActive=10
关于driver
属性的配置:在mysql-connector-java的jar包中,展开,找到META-INF下的services下的java.sql.Driver
文件,该文件中记录的就是正确的驱动类,如果没有该文件,则使用com.mysql.jdbc.Driver
。
然后,可以在Spring的配置文件中,通过<util:properties>
节点读取以上配置文件:
<!-- 读db.properties配置 -->
<util:properties id="dbConfig" location="classpath:db.properties"/>
在实际连接时,应该使用BasicDataSource
对象获取连接对象,在使用之前需要为BasicDataSource
的相关属性注入值:
<!-- 配置数据源 -->
<bean class="org.apache.commons.dbcp.BasicDataSource">
<property name="url" value="#{dbConfig.url}"/>
<property name="driverClassName" value="#{dbConfig.driver}"/>
<property name="username" value="#{dbConfig.username}"/>
<property name="password" value="#{dbConfig.password}"/>
<property name="initialSize" value="#{dbConfig.initialSize}"/>
<property name="maxActive" value="#{dbConfig.maxActive}"/>
</bean>
以上配置中,各<property>
节点的name
属性的值来自BasicDataSource
中的属性名称,在Spring表达式例如#{dbConfig.driver}
中的driver
来自db.properties
文件中各等于号左侧的属性名。
完成后,就可以编写单元测试,尝试获取连接对象:
public class Tests {
@Test
public void getConnection() throws SQLException {
ClassPathXmlApplicationContext ac
= new ClassPathXmlApplicationContext(
"spring.xml");
DataSource dataSource
= ac.getBean("dataSource", DataSource.class);
Connection conn = dataSource.getConnection();
System.out.println(conn);
ac.close();
}
}
接口与抽象方法
在开发持久层功能时,需要为对应的功能定义抽象方法,这些抽象方法应该存在于接口中,所以,先创建对应的接口文件cn.tedu.mybatis.UserMapper
:
public interface UserMapper {
}
然后,在接口中添加抽象方法,关于抽象方法的设计原则:
- 如果方法对应的功能是执行增、删、改,可以使用
Integer
作为返回值类型,表示“受影响的行数”,如果不关心返回值,也可以使用void
,如果方法对应的功能是查询,返回值可以根据实际使用需求来设计; - 方法的名称可以自定义,但绝不可以重载;
- 方法的参数按需设计。
以“增加新用户数据为例”,可以设计为:
Integer addnew(User user);
则还应该在项目补充创建cn.tedu.mybatis.User
类。
然后,还需要在Spring的配置文件中,配置org.mybatis.spring.mapper.MapperScannerConfigurer
类,以设置接口文件的位置:
<!-- MapperScannerConfigurer -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.tedu.mybatis"/>
</bean>
配置SQL
从http://doc.tedu.cn/config/mybatis-mapper.zip
下载配置SQL语句的文件的压缩包,解压后得到SomeMapper.xml文件,将其重命名为UserMapper.xml。
在src/main/resources/下创建名为mappers的文件夹,然后将UserMapper.xml粘贴到这个文件夹中。
然后编辑UserMapper.xml
,首先,添加<mapper>
根节点,并配置namespace
属性,取值是该XML文件对应的JAVA接口文件:
<!-- namespace:对应的接口 -->
<mapper namespace="cn.tedu.mybatis.UserMapper"></mapper>
然后,添加子级节点,以配置与接口中抽象方法对应的SQL语句,应该根据所需要执行的操作类型来决定使用<insert>
、<delete>
、<update>
、<select>
节点中的某一种,子点的id
属性就是对应的抽象方法的名称,然后,在节点内部配置所需要执行的SQL语句:
<!-- id:抽象方法的名称 -->
<insert id="addnew">
INSERT INTO t_user (
username,password,
age,phone,
email
) VALUES (
#{username},#{password},
#{age},#{phone},
#{email}
)
</insert>
如果配置的是<select>
节点,还必须配置resultType
属性,用于表示返回值类型,如果返回值类型是List
集合,则该属性配置值是List
集合中的元素类型。
最后,还需要在Spring的配置文件中,配置org.mybatis.spring.SqlSessionFactoryBean
类,用于指定:XML文件的位置,指定数据源:
<!-- SqlSessionFactoryBean -->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- XML文件在哪里 -->
<property name="mapperLocations"
value="classpath:mappers/*.xml"/>
<!-- 用哪个数据源 -->
<property name="dataSource"
ref="dataSource"/>
</bean>
验证以上功能是否完成,可以编写并执行单元测试:
@Test
public void addnew() {
ClassPathXmlApplicationContext ac
= new ClassPathXmlApplicationContext(
"spring.xml");
UserMapper userMapper
= ac.getBean("userMapper", UserMapper.class);
User user = new User();
user.setUsername("MyBatis");
user.setPassword("888888");
Integer rows = userMapper.addnew(user);
System.out.println("rows=" + rows);
ac.close();
}
查询时,需要查询结果中的列名与返回值类型中的属性名保持一致
假设在t_user
表中添加了新的名为is_delete
的字段:
alter table t_user add column is_delete int;
则对应的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;
当查询数据时,需要自定义别名,使得查询结果中的列名与返回值类型中的属性名保持一致:
<select id="findById"
resultType="cn.tedu.mybatis.User">
SELECT
id,username,
password,age,
phone,email,
is_delete AS isDelete
FROM
t_user
WHERE
id=#{id}
</select>
简单的关联表查询数据
假设需要创建“部门信息表”:
CREATE TABLE t_department (
id INT AUTO_INCREMENT COMMENT '部门id',
name VARCHAR(30) UNIQUE NOT NULL COMMENT '部门名称',
PRIMARY KEY(id)
) DEFAULT CHARSET=utf8;
然后,添加一些模拟数据:
INSERT INTO t_department (name) VALUES ('软件研发部'),('人力资源部'),('财务部'),('销售部');
并且,每个用户都归属于某个部门:
ALTER TABLE t_user ADD COLUMN department_id INT;
最后,为用户分配部门:
UPDATE t_user SET department_id=1 WHERE id IN (21,28,32);
UPDATE t_user SET department_id=2 WHERE id IN (24,25,26);
UPDATE t_user SET department_id=3 WHERE id IN (22,27,30);
UPDATE t_user SET department_id=4 WHERE id IN (23,29,31);
假设存在需求“查询某用户的信息,并显示该用户的部门的名称”,必须通过关联查询才可以得到所需要的结果,需要执行的SQL语句大致是:
select
*
from
t_user
left join
t_department
on
t_user.department_id=t_department.id
where
t_user.id=25;
如果需要使用MyBatis开发该功能,首先,目前并没有某个类型可以封装查询结果!因为创建的User
类是与t_user
表相对应的,例如User
这样的类称之为实体类(entity)
,实体类肯定不满足多表查询需求的,所以,对于这种情况,需要自行另创建VO类(value object),这种类的设计应该与查询结果或查询需求相对应:
public class UserVO {
private Integer id;
private String username;
private String password;
private Integer age;
private String phone;
private String email;
private Integer isDelete;
private Integer departmentId;
private String departmentName;
}
VO类的设计方式与实体类基本一致,只是这2种类的定位不同而已,实体类是与数据表对应的,VO类是与查询结果对应的。
然后,可以将此前的findById(Integer id)
的返回值类型修改为UserVO
:
UserVO findById(Integer id);
且映射的配置:
<select id="findById"
resultType="cn.tedu.mybatis.UserVO">
SELECT
t_user.id,username,
password,age,
phone,email,
is_delete AS isDelete,
department_id AS departmentId,
name AS departmentName
FROM
t_user
LEFT JOIN
t_department
ON
t_user.department_id=t_department.id
WHERE
t_user.id=#{id}
</select>
使用resultMap处理1对多的关联查询
假设存在需求“查询某部门信息,且显示出该部门所有的员工”,需要执行的SQL语句大致是:
select
*
from
t_department
left join
t_user
on
t_department.id=t_user.department_id
where
t_department.id=?
这类查询可能查到多条结果,取决于该部门的员工数量,但是,查询的需求却是查询“某1个”部门的信息!在设计查询的方法时,就需要某1个类型可以封装多条查询结果,可以:
public class DepartmentVO {
private Integer id;
private String name;
private List<User> users;
}
则对应的抽象方法可以是:
public interface DepartmentMapper {
DepartmentVO findById(Integer id);
}
此次查询必然可能出现多个结果,MyBatis并不知道如何将多个结果封装到1个对象中,所以,按照此前的查询做法,必然会出错!例如:
Caused by: org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 3
在这种情况下,需要自定义<resultMap>
节点,用于指导MyBatis如何将结果进行封装:
<mapper>
<!-- id:自定义名称 -->
<!-- type:返回结果的类型 -->
<resultMap id="DepartmentVO_Map"
type="cn.tedu.mybatis.DepartmentVO">
<!-- id节点:用于配置主键 -->
<!-- result节点:用于配置非主键 -->
<!-- column:查询结果的列名 -->
<!-- property:返回结果类型中的属性名 -->
<!-- 无论哪个节点,都是用于告之MyBatis将查询结果中哪一列的数据放到返回类型中的哪个属性中 -->
<id column="id" property="id"/>
<result column="name" property="name"/>
<!-- collection节点:用于配置1对多关系 -->
<!-- ofType:集合中的元素类型 -->
<collection property="users"
ofType="cn.tedu.mybatis.User">
<id column="uid" 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"/>
<result column="is_delete" property="isDelete"/>
</collection>
</resultMap>
<select id="findById"
resultMap="DepartmentVO_Map">
SELECT
t_department.id, name,
t_user.id AS uid, username,
password, age,
phone, email,
is_delete
FROM
t_department
LEFT JOIN
t_user
ON
t_department.id=t_user.department_id
WHERE
t_department.id=#{id}
</select>
</mapper>
查询结果例如:
DepartmentVO [
id=1, name=软件研发部,
users=[
User [id=21, username=Mike01, password=8888, age=18, phone=13800138001, email=Mike@qq.com, isDelete=0],
User [id=28, username=Mike08, password=8888, age=25, phone=13800138001, email=Mike@qq.com, isDelete=0],
User [id=32, username=Mike11, password=8888, age=26, phone=13800138001, email=Mike@qq.com, isDelete=0]
]
]
动态SQL
动态SQL指的是可以在配置SQL语句添加一些特殊的标签,例如<if>
、<foreach>
等,可以根据参数的不同,最终生成不同的SQL语句,则称之为动态SQL。
例如:根据若干个id删除数据,大致的SQL语句是:
delete from t_user where id in (1,3,5,7,9)
在实际应用中,以上IN
关键字后侧的括号中的值是不确定的,不光是值本身,值的数量也是不确定,并且各值之间需要使用逗号进行分隔,当值的数量不确定时,逗号的数量也是无法确定的!
首先,需要在UserMapper.java
接口中添加抽象方法:
Integer deleteByIds(List<Integer> ids);
在设计参数时,可以使用List
集合类型,也可以使用数组类型。
然后,在UserMapper.xml
中配置以上抽象方法的映射:
<delete id="deleteByIds">
DELETE FROM t_user
WHERE id IN (
<foreach collection="list"
item="id" separator=",">
#{id}
</foreach>
)
</delete>
在配置<foreach>
节点时:
collection
:需要被遍历的集合或数据,如果抽象方法只有1个参数时,如果参数的类型是List
集合,则取值为list
,如果参数类型是数组,则取值为array
;如果抽象方法有多个参数,则该属性取值为@Param("xx")
注解中使用的名称。item
:遍历过程中,集合中的元素的名称,在<foreach>
子级位置,可以使用#{item值}
表示被遍历到的元素的值。separator
:分隔符。open
和close
:遍历生成的SQL语句部分的最左侧字符和最右侧字符。
#{}与${}占位符
在MyBatis中,配置SQL语句时,可以使用#{}
与${}
这2种占位符。
使用#{}
占位符,可以用于占位某些值,也就是在SQL中写值的位置,都可以使用这种占位符(此前在学习JDBC时使用?的位置);而${}
可以表示SQL语句的任何部分!
在使用#{}
对某个值进行占位时,框架对整个SQL语句是有预编译处理的,无需考虑该值的数据类型的问题;而使用${}
占位时,框架的处理方式其实就是非常单纯的字符串拼接,需要考虑数据类型的问题,如果占位的值中包括字符串类型的值,则必须使用''
框住值!
由于#{}
只能对某个值进行占位,SQL语句本身是相对固定的,所以,这种做法实现的功能的局限性就非常明显,由于是预编译的,没有SQL注入风险,且工作效率较高!而${}
可以随意占位,功能可以非常灵活,但是,不是预编译的,有SQL注入风险,工作效率较低。