MyBatis简单使用
Posted cgy-home
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MyBatis简单使用相关的知识,希望对你有一定的参考价值。
### 1. MyBatis
MyBatis是一种持久层框架。
传统的JDBC开发中,需要程序员编写大量的代码,例如创建与数据库的连接 > 获取Statement/PreparedStatement对象 > 执行SQL语句 > 获取结果 > 处理结果 > 释放资源……这个流程相对固定,代码量略多。
使用MyBatis可以极大程度的简化开发过程,程序员只需要定义需要执行的任务对应的抽象方法,并指定该方法对应的SQL语句即可。
### 2. 使用MyBatis
#### 2.1. 案例目标
通过MyBatis实现对某张用户数据表的增、删、改、查。
#### 2.2. 创建数据库与数据表
首先,创建数据库:
CREATE DATABASE tedu_ums;
然后,使用该数据库:
USE tedu_ums
接下来,创建数据表:
CREATE TABLE t_user ( id INT AUTO_INCREMENT COMMENT ‘用户id‘, username VARCHAR(20) UNIQUE NOT NULL COMMENT ‘用户名‘, password VARCHAR(20) NOT NULL COMMENT ‘密码‘, age INT COMMENT ‘年龄‘, phone VARCHAR(20) COMMENT ‘手机号码‘, email VARCHAR(50) COMMENT ‘电子邮箱‘, PRIMARY KEY(id) ) DEFAULT CHARSET=UTF8;
#### 2.3. 创建项目
创建新的`Maven Project`,`Group Id`为`cn.tedu.mybatis`,`Artifact Id`为`MyBatis`,`Packaging`选择`war`。
创建完成后,依然需要:生成`web.xml`;添加Tomcat运行环境;复制此前项目中的依赖;复制Spring的配置文件;复制`web.xml`中关于`DispatcherServlet`和`CharacterEncodingFilter`的配置。
此次需要使用MyBatis框架,所以需要添加新的依赖:
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.1</version> </dependency>
由于即将把MyBatis和Spring框架整合起来使用,所以需要添加整合2个框架的依赖:
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.1</version> </dependency>
其工作核心是依赖于JDBC的,还需要添加`spring-jdbc`依赖,该依赖的版本必须与当前项目使用的`spring-webmvc`的版本完全一致:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.3.9.RELEASE</version> </dependency>
本次使用的是mysql数据库,还需要添加数据库连接的jar包:
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.15</version> </dependency>
另外,还需要数据库连接池的依赖:
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
#### 2.4. 连接数据库
首先,应该在`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=10 maxActive=50
如果不清楚`driver`属性的值,可以展开jar包中找到配置文件来查看:
然后,在Spring的配置文件中,使用`<util:properties>`节点读取以上配置文件:
<!-- 读取db.properties中的配置 --> <util:properties location="classpath:db.properties" />
读取的配置需要注入到数据库连接池的`BasicDataSource`对象中,所以,还要在Spring的配置文件中配置该类对应的`<bean>`节点,并注入属性的值,由于需要读取此前的`<util:properties>`中的值,所以,还要为该节点添加`id`:
<!-- 读取db.properties中的配置 --> <util:properties id="dbConfig" location="classpath:db.properties" />
<!-- 配置数据源 --> <bean id="dataSource" 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>
相关文件的配置关系:
然后,创建单元测试类,并测试连接是否可用:
@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();
#### 2.5. 接口与抽象方法
创建`cn.tedu.mybatis.mapper.UserMapper`接口文件。
创建`cn.tedu.mybatis.entity.User`实体类。
首先,完成“增加”操作,则添加抽象方法:
// INSERT INTO t_user (username,password,age,phone,email) VALUES (?,?,?,?,?) Integer addnew(User user);
MyBatis的接口文件不必在Spring组件扫描的范围之内,即框架本身是不知道有哪些接口文件、这些文件在哪里的!所以,需要配置这些接口文件所在的位置!则在Spring的配置文件中配置`MapperScannerConfigurer`并为必要的属性注入值:
<!-- MapperScannerConfigurer --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!-- 持久层接口在哪个包中 --> <property name="basePackage" value="cn.tedu.mybatis.mapper" /> </bean>
#### 2.6. 配置SQL语句
首先,从`doc.tedu.cn`下载**MyBatis Mapper映射文件 mybatis-mapper.zip**文件,解压得到`SomeMapper.xml`文件。
在`src\main\resources`创建名为`mappers`的文件夹,并将`SomeMapper.xml`文件复制到这个文件夹中:
![](03.png)
然后,编辑`SomeMapper.xml`文件,配置抽象方法对应的SQL语句:
<!-- namespace:该XML文件对应哪个接口文件 --> <mapper namespace="cn.tedu.mybatis.mapper.UserMapper"> <!-- 根据将要执行的SQL语句选择节点 --> <!-- id:抽象方法的名称 --> <insert id="addnew"> INSERT INTO t_user ( username,password, age,phone, email ) VALUES ( #username,#password, #age,#phone, #email ) </insert> </mapper>
配置的关系图为:
至此,还需要让框架知道这些XML文件在哪里,并且,找到该文件后还要执行所配置的SQL语句,就需要让框架知道如何连接数据库,则还需要配置`SqlSessionFactoryBean`:
<!-- SqlSessionFactoryBean --> <bean class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 如何连接数据库 --> <property name="dataSource" ref="dataSource" /> <!-- 配置的XML文件在哪里 --> <property name="mapperLocations" value="classpath:mappers/*.xml" /> </bean>
最后,可以编写并执行单元测试:
@Test public void addnew() ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml"); UserMapper userMapper = ac.getBean("userMapper", UserMapper.class); User user = new User(); user.setUsername("FanCQ"); user.setPassword("123456"); Integer rows = userMapper.addnew(user); System.out.println("rows=" + rows); ac.close();
### 3. 其它数据访问方式
#### 3.1. 根据id删除数据
在`UserMapper.java`接口中添加新的抽象方法:
// delete from t_user where id=? Integer deleteById(Integer id); 然后,在`UserMapper.xml`中配置映射: <delete id="deleteById"> DELETE FROM t_user WHERE id=#id </delete>
#### 3.2. 查询指定id的用户数据
在`UserMapper.java`接口中添加新的抽象方法:
// select * from t_user where id=? User findById(Integer id); 然后,在`UserMapper.xml`中配置映射: <select id="findById" resultType="cn.tedu.mybatis.entity.User"> SELECT id,username, password,age, phone,email FROM t_user WHERE id=#id </select>
#### 3.3. 查询所有用户数据
在`UserMapper.java`接口中添加新的抽象方法:
// select * from t_user
List<User> findAll();
然后,在`UserMapper.xml`中配置映射:
<select id="findAll" resultType="cn.tedu.mybatis.entity.User"> SELECT id,username, password,age, phone,email FROM t_user </select>
### 4. MyBatis的设计原则
#### 4.1. 如何设计抽象方法
在MyBatis中,接口中的抽象方法的设计原则有:
1. 返回值:如果将要执行的是增、删、改操作,返回值应该是`Integer`,表示受影响的行数,如果将要执行的是查询操作,返回值可以是符合查询结果需求的数据类型;
2. 方法名称:抽象方法的名称可以自定义,但是,不允许使用重载机制,具体的命名方式推荐遵守JPA的规范;
3. 参数列表:抽象方法的参数可根据将要执行SQL语句中的变量来决定,如果执行的是`INSERT`操作,参数应该是自定义的数据类型,可以封装所有字段的值。
**注意:默认情况下,抽象方法只允许有最多1个参数!**
#### 4.2. 如何配置XML中的映射
所有的增删改查节点都必须指定`id`属性。
如果配置是的`<select>`节点,除了必须指定`id`属性以外,还必须配置`resultType`或`resultMap`中的1个。
### 5.1. 在插入数据时获取新数据的id
如果需要在插入数据时,直接获取到新数据的id(自动编号的字段的值),需要在配置XML中的映射时,添加配置`useGeneratedKeys`和`keyProperty`这2项新的属性:
<!-- 根据将要执行的SQL语句选择节点 --> <!-- id:抽象方法的名称 --> <!-- useGeneratedKeys:是否需要获取自动生成的字段的值 --> <!-- keyProperty:获取到了自动生成的值后放到哪个属性中 --> <insert id="addnew" useGeneratedKeys="true" keyProperty="id"> INSERT INTO t_user ( username,password, age,phone, email ) VALUES ( #username,#password, #age,#phone, #email ) </insert>
<!-- 根据将要执行的SQL语句选择节点 --> <!-- id:抽象方法的名称 --> <!-- useGeneratedKeys:是否需要获取自动生成的字段的值 --> <!-- keyProperty:获取到了自动生成的值后放到哪个属性中 --> <insert id="addnew" useGeneratedKeys="true" keyProperty="id"> INSERT INTO t_user ( username,password, age,phone, email ) VALUES ( #username,#password, #age,#phone, #email ) </insert>
当MyBatis执行时,就会获取自动编号的值,并封装到插入数据时的参数对象中,例如:
System.out.println("插入数据之前:" + user); Integer rows = userMapper.addnew(user); System.out.println("rows=" + rows); System.out.println("插入数据之后:" + user);
实际运行结果为:
插入数据之前:User [id=null, username=WangKJ, password=123456, age=18, phone=13900139001, email=[email protected]] rows=1 插入数据之后:User [id=21, username=WangKJ, password=123456, age=18, phone=13900139001, [email protected]]
所以,当需要获取自动编号的值时,添加以上2个属性的配置,并当插入完成之后,从参数对象中获取值即可。
### 5.2. 查询时自定义别名
为`t_user`表添加新的字段`is_delete INT COMMENT "是否标记为删除,0-未删除,1-已删除"`:
ALTER TABLE t_user ADD COLUMN is_delete INT COMMENT "是否标记为删除,0-未删除,1-已删除";
由于数据表中添加了新字段,则实体类`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; ... ...
为了保证功能正常,在插入数据对应的SQL映射中,也要添加该字段的配置:
<insert id="addnew" useGeneratedKeys="true" keyProperty="id"> INSERT INTO t_user ( username, password, age, phone, email, is_delete ) VALUES ( #username, #password, #age, #phone, #email, #isDelete ) </insert>
并且,在查询时,也要查询`is_delete`字段的值,需要注意的是:MyBatis要求查询结果的列名与类的属性名一致,才可以正确的封装查询结果到对象中,如果不一致,可以取别名:
<select id="findById" resultType="cn.tedu.mybatis.entity.User"> SELECT id,username, password,age, phone,email, is_delete AS isDelete FROM t_user WHERE id=#id </select>
### 6. 动态SQL
动态SQL指的是在配置XML映射时,可以使用一些特定的标签实现一些逻辑,例如`<if>`标签、`<foreach>`标签等,则可以根据参数不同,生成不同的SQL语句。
案例目标:批量删除指定的一系列id的数据,例如一次性删除id为3/6/9的数据。
需要执行的SQL语句大致是:
delete from t_user where id in (3,6,9);
在以上SQL语句,`(3,6,9)`可能随着用户的操作不同,而产生变化,无论是其中的值,还是值的数量,都可能不同!
所以,在设计抽象方法时:
Integer deleteByIds(List<Integer> ids);
然后,在配置XML映射时:
<delete id="deleteByIds"> DELETE FROM t_user WHERE id IN ( <foreach collection="list" item="id" separator=","> #id </foreach> ) </delete
以上配置中:
- `collection`:被遍历的参数,如果该参数是`List`类型的,则该属性值为`list`,如果参数是数组类型的,则该属性是`array`;
- `item`:遍历过程中的变量名,在`<foreach>`节点内部,也用该名称表示被遍历的数据;
- `separator`:分隔符;
- `open`:遍历生成的代码的最左侧的字符,例如可以是`(`,当然,前提是SQL语句中没有在动态SQL的最左侧使用`(`;
- `close`:遍历生成的代码的最右侧的字符,与`open`类似。
### 7. MyBatis中的占位符
在MyBatis中,允许使用`#`和`$`这2种格式的占位符,例如:
<select id="find" resultType="cn.tedu.mybatis.entity.User"> SELECT id,username, password,age, phone,email, is_delete AS isDelete 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>
使用`#`的占位符是预编译的,只能用于在SQL语句中写值的位置,例如`delete from t_user where id=#id`,也就是在传统的JDBC编程中,可以在`?`的位置,都可以使用这种占位符,反之,不可以写`?`的位置就不可以使用这种占位符!
使用`$`的占位符不是预编译,而是在编译之前进行字符串的拼接,基本上可以取代SQL语句中的任何部分,只要保证最终能够拼接出语法正确的SQL语句即可!
所以,使用`#`的是先编译,再替换值并执行,而使用`$`的先拼接,再编译,再执行。
如果使用`$`则不是预编译的,则存在SQL注入的安全隐患。
占位符小结:如果在使用传统的JDBC编程时,能写`?`的位置,都使用`#`,否则,使用`$`。
关于动态SQL的使用,绝不提倡使用某1个方法及其映射实现许多功能!多半会导致性能下降及相关资源浪费。
### 8. MyBatis中抽象方法的参数超过1个的问题
在MyBatis的抽象方法中,默认只支持1个参数,或没有参数!
如果直接声明某个抽象方法存在多个参数,在运行时,会出现错误:
Caused by:
org.apache.ibatis.binding.BindingException:
Parameter ‘password‘ not found.
Available parameters are [arg1, arg0, param1, param2]
可以将多个参数使用`Map`封装起来,实现“只有1个参数”,从而解决这个问题,但是,使用`Map`存在的问题:方法的调用者不明确需要向`Map`中封装几项数据,每项数据的`key`是多少等问题。
MyBatis允许在抽象方法的多个参数之前添加`@Param("名称")`,在底层实现时,会将多参数封装为`Map`,但是开发者不需要考虑这个问题,在实际编程时,只要某个抽象方法的参数超过1个,则需要为每个参数添加该注解,且在配置XML映射时,占位符中的名称必须是注解中配置的名称:
Integer updatePassword( @Param("id") Integer id, @Param("pwd") String password); <update id="updatePassword"> UPDATE t_user SET password=#pwd WHERE id=#id </update>
### 9. 多表关联查询
#### 9.1. 准备工作
创建“部门表”:
CREATE TABLE t_department ( id INT AUTO_INCREMENT COMMENT ‘部门id‘, name VARCHAR(20) NOT NULL COMMENT ‘部门名称‘, PRIMARY KEY (id) ) DEFAULT CHARSET=UTF8;
每个员工都应该归属某个部门,则在`t_user`表中添加新的字段,表示员工归属的部门:
ALTER TABLE t_user ADD COLUMN department_id INT;
接下来,需要修改`User`类,并创建新的`Department`类。
再完成一些测试数据:
INSERT INTO t_department (name) VALUES (‘软件研发部‘),(‘人力资源部‘),(‘财务部‘); UPDATE t_user SET department_id=1 WHERE id IN (1,11,12,19,20,21); UPDATE t_user SET department_id=2 WHERE id IN (2,10,13,16,22); UPDATE t_user SET department_id=3 WHERE id IN (5,7,14,15,23);
#### 9.2. 查询某用户信息时,直接显示该用户归属的部门的名称
需要执行的SQL语句应该是:
SELECT t_user.id,username, password, age, phone, email, is_delete, department_id, name FROM t_user LEFT JOIN t_department ON t_user.department_id=t_department.id WHERE t_user.id=10;
由于目前没有哪个数据类型可以封装此次的查询结果,则需要创建新的数据类型,以封装查询结果。通常,这种与数据表结构并不对应,却与实际使用相对应(与查询结果相对应)的类,称之为VO类,即Value Object类,所以,需要创建`cn.tedu.mybatis.vo.UserVO`类,并在类中声明与查询结果相对应的属性:
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; ... ...
由于查询主体是“用户”,所以,在`UserMapper.java`中添加抽象方法:
UserVO findUserById(Integer id);
配置的SQL映射为:
<select id="findUserById" resultType="cn.tedu.mybatis.vo.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>
最后,编写并执行单元测试:
@Test public void findUserById() Integer id = 21; UserVO userVO = userMapper.findUserById(id); System.out.println(userVO);
#### 9.3. 查询某部门信息时,显示该部门的员工列表
需要执行的SQL语句应该是:
SELECT * FROM t_department LEFT JOIN t_user ON t_department.id=t_user.department_id WHERE t_department.id=1;
以上查询可能查询出多条结果,但是,根据本意,其实只需要1个结果!
首先,应该创建新的VO类,以封装查询结果:
public class DepartmentVO private Integer id; private String name; private List<User> users; ... ...
由于此次查询的主体是“部门”,则应该先创建`cn.tedu.mybatis.mapper.DepartmentMapper`接口,然后在该接口中添加抽象方法:
DepartmentVO findById(Integer id);
然后,复制原有的`UserMapper.xml`,并粘贴回`mappers`文件夹,重命名为`DepartmentMapper.xml`,删除原有配置,然后配置映射对应以上接口与抽象方法:
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
由于查询结果应该只有1个对象,实际查询结果却有多条记录,需要自定义`resultMap`,用于指导MyBatis将多条查询结果封装到1个对象中:
<!-- resultMap用于指导MyBatis如何将查询结果封装到对象中 --> <!-- id:自定义名称 --> <!-- type:返回的对象的类型 --> <resultMap id="DepartmentVO_Result" type="cn.tedu.mybatis.vo.DepartmentVO"> <!-- id节点:用于配置主键字段 --> <!-- column:查询结果中的列名 --> <!-- property:返回的对象的属性名 --> <id column="id" property="id"/> <!-- result节点:用于配置非主键字段 --> <result column="name" property="name"/> <!-- collection节点:用于配置1对多的数据 --> <!-- property:返回的对象的属性名 --> <!-- ofType:集合中的元素是什么类型的 --> <collection property="users" ofType="cn.tedu.mybatis.entity.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>
以上是关于MyBatis简单使用的主要内容,如果未能解决你的问题,请参考以下文章