MyBatis框架的作用
MyBatis框架主要作用是简化持久层开发。当使用MyBatis处理数据增删改查时,只需要定义访问数据的抽象方法,并配置该抽象方法对应的SQL语句即可!
持久层:解决项目中数据持久化处理的组件。
数据持久化:将数据永久的保存下来,即将数据存储在硬件等可以永久保存数据的存储介质中,如果要将数据保存在这些存储介质中,数据需要以文件的形式存在,通常,可以将数据存到文本文档,XML文档,数据库…通常,在没有明确的说明的情况下,讨论的就是使用数据库存取数据。
内存(RAM,具体表现通常是内存条):是CPU与其它硬件交换数据的“桥梁”,正在执行的程序和数据都在内存中,一旦断电则数据就会全部丢失。
1.创建MyBatis项目&配置相关依赖
创建Maven项目,在项目中添加相关依赖:
<!-- java version(java依赖版本) -->
<java.version>1.8</java.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<!-- dependency version(集中管理各个依赖的版本,下面代码配置版本时只需要"${}"提取即可) -->
<mybatis.version>3.5.4</mybatis.version>
<mybatis.spring.version>2.0.5</mybatis.spring.version>
<spring.version>5.2.7.RELEASE</spring.version>
<mysql.version>8.0.12</mysql.version>
<druid.version>1.1.23</druid.version>
<junit.version>4.13</junit.version>
</properties>
<dependencies>
<!-- MyBatis:mybatis -->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<!-- MyBatis整合Spring:mybatis-spring -->
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis.spring.version}</version>
</dependency>
<!-- Spring:spring-context -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- Spring JDBC:spring-jdbc -->
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- MySQL:mysql-connector-java -->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- 连接池:druid -->
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<!-- 单元测试:junit -->
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
2.实现数据库连接配置
首先,需要在MySOL里创建数tedu_ums据库:
CREATE DATABASE tedu_ums;
然后,在项目中,在src/main/resources下创建jdbc.properties文件,用于配置连接数据库的相关信息:
spring.datasource前缀只是用来提高辨识度的!!
spring.datasource.url=jdbc:mysql://localhost:3306/tedu_ums?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.initialSize=2
spring.datasource.maxActive=10
注意根据自己的MySQL名,密码进行配置!!!
在src/main/java下创建包cn.tedu.mybatis,并在包里面创建SpringConfig类,在该类中读取以上配置文件,并·添加方法将读取的信息配置给DataSource的对象,将对象交给框架处理,最后,还需要在配置类中配置SqlSessionFactoryBean
的对象,为该对象设置数据源,使得MyBatis框架能够自动获取数据库连接,并完成数据访问!:
package cn.tedu.mybatis;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.io.Resource;
import com.alibaba.druid.pool.DruidDataSource;
//确定根包位置
@MapperScan("cn.tedu.mybatis")
@PropertySource("classpath:jdbc.properties")
public class SpringConfig {
@Value("${spring.datasource.url}")
private String url;
@Value("${spring.datasource.driver-class-name}")
private String driverClassName;
@Value("${spring.datasource.username}")
private String username;
@Value("${spring.datasource.password}")
private String password;
@Value("${spring.datasource.initialSize}")
private Integer initialSize;
@Value("${spring.datasource.maxActive}")
private Integer maxActive;
@Value("${mybatis.mapper-locations}")
private Resource[] mapperlocations;
@Bean
public DataSource dataSource() {
// 当前项目添加依赖时
// 数据库连接池使用的是druid
// 所以,此处创建DruidDataSource的对象
// 如果以后改用其它数据库连接池
// 则创建其它数据库连接池中的对象即可
DruidDataSource ds = new DruidDataSource();
ds.setUrl(url);
ds.setDriverClassName(driverClassName);
ds.setUsername(username);
ds.setPassword(password);
ds.setInitialSize(initialSize);
ds.setMaxActive(maxActive);
return ds;
}
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(
DataSource dataSource) {
SqlSessionFactoryBean bean =
new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
bean.setMapperLocations(mapperlocations);
return bean;
}
}
3.创建数据表
在tedu_ums数据库中创建t_user数据表:
use tedu_ums;
CREATE TABLE t_user (
id int AUTO_INCREMENT,
username varchar(20) NOT NULL UNIQUE,
password varchar(20) NOT NULL,
age int,
phone varchar(20),
email varchar(30),
PRIMARY KEY (id)
) DEFAULT CHARSET=utf8mb4;
4.插入、删除、修改、查询数据
4.1定义抽象方法
当使用MyBatis框架处理增删改查的时候,会需要抽象方法,我们将抽象方法定义到一个接口中**(接口名推荐使用Mapper作为结尾)**:
在cn.tedu.mybatis包中创建UserMapper接口,这个接口用于声明访问“用户"数据表中的数据:
public interface UserMapper{}
然后在接口中声明”插入用户数据"的抽象方法,关于抽象方法的声明:
返回值: 如果需要执行的是增、删、改类型的,则需要使用Integer作为返回值的类型,表示受影响的行数,当然也可以使用void作为返回值类型,表示“不关系受影响的行数",但是,并不建议这么做,如果需要执行的数据操作是查询,设计为所期望的类型即可,当然这个类型需要能够查询到的数据封装进去!
方法名称: 自定义(见名知意)
参数列表: 根据需要执行的SQL语句中的参数来决定。
当需要执行”插入用户数据"操作时,需要执行的SQL语句是:
INSERT INTO t_user(username,password,age,phone,email) VALUES(?,?,?,?,?)
可以将以上的SQL语句中的参数全部声明到抽象方法参数列表中,例如:
Integer insert (String username, String password,Integer age ,String phone,String email)
推荐使用的是将SQL语句中的参数声明到一个实体类中,然后将实体类作为抽象方法的参数:
package cn.tedu.mybatis;
public class User {
private Integer id;
private String username;
private String password;
private Integer age;
private String phone;
private String email;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "User [id=" + id + ", username=" + username + ", password=" + password + ", age=" + age + ", phone="
+ phone + ", email=" + email + "]";
}
}
然后设计抽象方法:
Integer insert(User user);
抽象方法全部代码:
package cn.tedu.mybatis;
import java.util.ArrayList;
import java.util.List;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Options;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
public interface UserMapper {
Integer insert(User user);
// // 增删改返回值是受到影响的行数所以为Integer接收
Integer deleteById(Integer id);
Integer updatePassword(Integer password);
Integer updateEmailByid(@Param("email") String email, @Param("id") Integer id);
Integer count();
User findById(Integer id);
ArrayList<User> findAll();
Integer deleteByIds(Integer[] ids);
Integer updatePasswordByIds(@Param("password") String password, @Param("ids") List<Integer> ids);
}
目前,MyBatis框架并不知道接口文件的位置,就更加无法使用自定义的抽象方法,必须在配置类的声明之前添加@MapperScan
注解,以指定接口文件所在的包!
4.2使用XML文件配置SQL语句
在项目的src/mian/resources下创建mappers文件夹,并创建UserMapper.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=" ">
</mapper>
该文件的根节点时节点,必须配置namespace的属性,该属性的值是对应的MyBatis接口文件的全名,例如:
该例子就配置了(如果疑惑待会看项目文件结构理解):
<mapper namespace="cn.tedu.mybatis.UserMapper">
</mapper>
接下来,根据需要执行的SQL语句的种类,选择使用<insert>
、<delete>
、<update>
、<select>
这4个节点中的某1个来配置SQL语句,这些节点都必须配置id
属性,该属性的值就是接口中的抽象方法的名称,然后,在节点内部编写SQL语句即可**(UserMapper)完整代码** :
<?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="cn.tedu.mybatis.UserMapper">
<!--id标识的是抽象方法的名称,获取自动编号的id值 useGeneratedKeys="true" keyProperty="id"-->
<!--插入一条数据 -->
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
INSERT
t_user(username,password,age,phone,email)
VALUES(#{username},#{password},#{age},#{phone},#{email})
</insert>
<!--根据id删除信息 -->
<delete id="deleteById">
DELETE FROM t_user WHERE id=#{id}
</delete>
<!--修改密码 -->
<update id="updatePassword">
UPDATE t_user SET password=#{password}
</update>
<!--根据id修改邮箱 -->
<update id="updateEmailByid">
UPDATE t_user SET email=#{email} WHERE id=#{id}
</update>
<!--统计表中的数据条数 -->
<select id="count" resultType="java.lang.Integer">
SELECT COUNT(*) FROM t_user
</select>
<!--根据id查询信息 -->
<select id="findById" resultType="cn.tedu.mybatis.User">
SELECT * FROM t_user WHERE id=#{id}
</select>
<!--查询所有id分组 -->
<select id="findAll" resultType="cn.tedu.mybatis.User">
SELECT * FROM t_user ORDER BY id
</select>
<!--根据id批量删除 -->
<delete id="deleteByIds">
delete from t_user where id in(
<foreach collection="array" item="id" separator=",">
#{id}
</foreach>
)
</delete>
<update id="updatePasswordByIds">
update t_user set password=#{password} where id in(
<foreach collection="ids" item="id" separator=",">
#{id}</foreach>
)
</update>
</mapper>
resultType`指的就是“封装查询结果的数据的类型”,也可以理解为“抽象方法的返回值的类型”,例如可以配置为:
<select id="count" resultType="java.lang.Integer">
SELECT COUNT(*) FROM t_user
</select>
如果某个查询的抽象方法的返回值是List
集合类型的,例如:
List<User> findAll();
在配置<select>
的resultType
属性时,该属性值必须是集合中的元素的类型,例如:
<select id="findAll" resultType="cn.tedu.mybatis.User">
SELECT * FROM t_user ORDER BY id
</select>
至于resultMap
属性,后面专门介绍。
注意:使用了这种做法后,就需要对抽象方法名称的定义增加一个要求“不允许重载”!
4.2.1 关于多参数的问题
当抽象方法的参数列表中超过1个参数时,在配置SQL语句时直接使用#{参数名称}
是无法访问到参数值的!
因为Java源文件在运行之前需要被编译成字节码文件(.class文件),编译时,会丢失所有局部的量的名称,所以,会导致运行时原有的“参数名称”无法使用的问题!
MyBatis允许使用arg
作为前缀并添加从0
开始编号的名称(例如arg0
、arg1
等等)表示第?个参数的名称,后续,在配置SQL语句时,就可以通过例如#{arg0}
来表示抽象方法的第1个参数的值,使用#{arg1}
表示抽象方法的第2个参数的值……以此类推!另外,还可以使用param
作为前缀并添加从1
开始编号的名称(例如param1
、param2
等等),在具体使用时,使用arg
系列的名称和param
系列的名称均可!
但是,使用arg
和param
系列的名称不便于表示语义,并且,当抽象方法的参数列表发生变化时,这些名称中的序号也可能需要调整!
MyBatis提供了@Param
注解,这个注解是添加在抽象方法的各参数之前的,可以在该注解中指定名称,后续,在配置SQL语句时,占位符中就使用注解中配置的名称!
4.2.2 动态SQL – foreach
假设存在需求:批量删除用户数据(一次性删除若干条用户数据);
需要执行的SQL语句大致是:
DELETE FROM t_user WHERE id=? OR id=? OR id=?;
DELETE FROM t_user WHERE id IN (?,?,?);
作为开发人员,无法确定以上SQL语句中问号的数量,及问号对应的参数值!只能确定以上参数的数据类型及所表示的意义!
以上功能最终将由用户(软件的使用者)来决定需要删除的数据的数量(问号的数量),及删除的数据是哪几条(问号对应的参数值)!就会导致“当用户的操作不同时(选中需要删除的数据不同),最终需要执行的SQL语句是不同的”!MyBatis框架提供了“动态SQL”机制来解决这个问题!
动态SQL:根据用户提供的参数值不同,最终需要执行的SQL语句可以不同!
当需要实现以上批量删除的需求时,可以将抽象方法设计为:
Integer deleteByIds(List<Integer> ids);
或者,也可以设计为(本次案例就使用这个):
Integer deleteByIds(Integer[] ids);
甚至,还可以设计为:
Integer deleteByIds(Integer... ids);
可变参数在被处理时,本质上就是数据。
在配置SQL语句时,需要通过<foreach>
节点来配置SQL语句中需要通过循环生成的部分:
<delete id="deleteByIds">
DELETE FROM t_user WHERE id IN (
<foreach collection="array" item="id" separator=",">
#{id}
</foreach>
)
</delete>
关于<foreach>
节点的配置:
-
collection
:需要被遍历的对象,当抽象方法的参数只有1个且没有添加@Param
注解时,如果参数类型是List
集合,则取值为list
,如果参数类型是数组,则取值为array
;当抽象方法的参数超过1个,就一定添加了@Param
注解,则取值为@Param
注解配置的参数值; -
item
:遍历过程中的每一个元素数据,当前属性可以自定义值表示元素数据的名称,在<foreach>
节点的子级,使用#{}
占位符时,就可以使用这个名称来表示数据; -
separator
:遍历生成的代码片段中,各元素数据之间的分隔符号。
5.添加测试文件
接下来,在src/test/java下创建cn.tedu.mybatis
包,并在这个包中创建``UserMapperTests`测试类,并测试以上方法:
package cn.tedu.mybatis;
import java.util.ArrayList;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class UserMapperTests {
private AnnotationConfigApplicationContext ac;
private UserMapper userMapper;
// 插入数据
@Test
public void insert() {
User user = new User();
user.setUsername("poot");
user.setPassword("1234");
user.setAge(22);
user.setPhone("12344556677");
user.setEmail("root@.com");
System.out.println(user);
Integer rows = userMapper.insert(user);
System.out.println(user);
System.out.println("插入了" + rows + "条数据!");
}
// 删除数据
@Test
public void delete() {
Integer id = 1;
Integer rows = userMapper.deleteById(id);
System.out.println("删除了" + rows + "条数据!");
}
// 更新数据
@Test
public void update() {
Integer password = 1;
Integer rows = userMapper.updatePassword(password);
System.out.println("更新了" + rows + "条数据!");
}
// 跟据id修改邮箱
@Test
public void updateEmailByid() {
String email = "123wewe@.com";
Integer id = 3;
Integer rows = userMapper.updateEmailByid(email, id);
System.out.println("成功修改了" + rows + "条数据!");
}
//统计表中人数
@Test
public void count() {
Integer count = userMapper.count();
System.out.println("表中有" + count + "条数据");
}
// 通过id查询
@Test
public void findById() {
Integer id = 3;
User user = userMapper.findById(id);
System.out.println("user=" + user);
}
// 表中人的所有信息
@Test
public void findAll() {
ArrayList<User> array = userMapper.findAll();
System.out.println("array=" + array.size());
}
// 根据id批量删除
@Test
public void deleteByIds() {
Integer[] ids = { 4, 5, 8 };
Integer rows = userMapper.deleteByIds(ids);
System.out.println("一共删除了用户表的" + rows + "个字段");
}
// 根据id批量修改
@Test
public void updatePasswordByIds() {
List<Integer> ids = new ArrayList<Integer>();
ids.add(6);
ids.add(9);
String password = "abcd";
Integer rows = userMapper.updatePasswordByIds(password, ids);
System.out.println("一共修改了用户表的" + rows + "个字段");
}
// 资源加载,数据库操作前先加载
@Before
public void doBefore() {
ac = new AnnotationConfigApplicationContext(SpringConfig.class);
userMapper = ac.getBean("userMapper", UserMapper.class);
}
// 资源关闭,数据库操作完后关闭
@After
public void doAfter() {
ac.close();
}
}