Mabatis学习与使用

本文最后更新于:2021年10月13日 凌晨

概览:Mybatis

Mybatis

数据库 student表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 # 创建一个数据库 springdb
create database springdb character set utf8;

# 创建一个表 student
create table `student`(
`id` int AUTO_INCREMENT NOT NULL,
`name` varchar(80) COMMENT '学生姓名',
`email` varchar(80) COMMENT '邮箱',
`age` int COMMENT '年龄',
primary key(`id`)
)charset=utf8;

# 插入两条数据
insert into student(`id`,`name`,`email`,`age`)values(1,'小','xiaoming@qq.com',3);
insert into student(`id`,`name`,`email`,`age`)values(2,'小红','xiaohong@qq.com',23);

# 查询结果
mysql> select * from student;
+----+------+-----------------+------+
| id | name | email | age |
+----+------+-----------------+------+
| 1 | 小 | xiaoming@qq.com | 3 |
| 2 | 小红 | xiaohong@qq.com | 23 |
+----+------+-----------------+------+

Mybatis框架

整体目录结构:

pom.xml配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<dependencies>
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
</dependencies>

<build>
<!--资源插件,自动扫描当前目录下的所有配置文件-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>

</build>
  • resources标签下的扫描当前目录下的所有配置文件是必须的。否则项目运行就是显示com.learm.dao.StudentDao.xml找不到!

核心配置文件 mybatis.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--设置日志
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>-->

<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<!--配置数据源: 创建Connection对象。-->
<dataSource type="POOLED">
<!--driver:驱动的内容-->
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!--连接数据库的url-->
<property name="url" value="jdbc:mysql://localhost:3306/springdb?useUnicode=true&amp;characterEncoding=utf-8"/>
<!--用户名-->
<property name="username" value="root"/>
<!--密码-->
<property name="password" value="1234"/>
</dataSource>
</environment>
</environments>

<!--指定其他mapper文件的位置:
其他其他mapper文件目的是找到其他文件的sql语句
-->
<mappers>
<!--
使用mapper的resource属性指定mapper文件的路径。
这个路径是从target/classes路径开启的

使用注意:
resource=“mapper文件的路径,使用 / 分割路径”
一个mapper resource 指定一个mapper文件
-->
<mapper resource="com/learn/dao/StudentDao.xml"/>

</mappers>
</configuration>

重要的点:

  1. 前面链接中应当是mybatis-3-config.dtd,这表示一个配置文件的内容规范。
  2. 配置数据源中,<property name="driver" value="com.mysql.cj.jdbc.Driver"/>driver表示驱动,配置带cj的,而非不带cj的,不带cj的已经不推荐使用了。
  3. 连接数据库的url中,springdb是数据库名字,&amp;是HTML实体,表示&符号。
  4. 使用<mappers>标签来引入mapper文件。可以引入多个mapper文件,一行一个。

实体类Student

和数据库中的字段类型和名称保持一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.learn.domain;

public class Student {

// 与数据库表中的列名保持一致,当然也可以不一致,需要额外配置其他文件
private Integer id;
private String name;
private String email;
private Integer age;

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
", age=" + age +
'}';
}
}

StudentDao

  • 是一个接口
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.learn.dao;

import com.learn.domain.Student;

public interface StudentDao {

/**
* 根据学生id来查询学生信息
* @param id
* @return
*/
Student queryStudentById(Integer id);
}
  • 数据持久层,通过这个接口来调用获取数据。

StudentDao.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?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">

<!--
namespace 推荐使用完全路径来做唯一标识
-->

<mapper namespace="com.learn.dao.StudentDao">

<!--查询一个学生Student

<select>:表示查询操作, 里面是select 语句
id: 要执行的sql语句的唯一标识, 是一个自定义字符串。
推荐使用dao接口中的方法名称
resultType:告诉mybatis,执行sql语句,把数据赋值给那个类型的java对象。
resultType的值现在使用的java对象的全限定名称

#{studentId}:占位符, 表示从java程序中传入过来的数据
-->
<select id="selectStudentById" resultType="com.learn.domain.Student">
select id,name,email,age from student where id = #{studentId}
</select>

</mapper>
  • mybatis-3-mapper.dtd来表示是一个mapper文件。
  • namespace是命名空间,推荐使用类路径来做唯一标识。
  • select语句不要有;结尾。id属性就是Dao接口中的方法名称,resultType的值使用的java对象的全限定名称。
  • #{studentId}是占位符,内部的字符串内容是任意的,在执行的时候会被替换成传递过来的参数值。

测试类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import com.learn.domain.Student;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;

public class MyTest {

@Test
public void testQueryStudentById() throws IOException {

//调用mybatis某个对象的方法,执行mapper文件中的sql语句
//mybatis核心类: SqlSessionFactory

//1.定义mybatis主配置文件的位置, 从类路径开始的相对路径
String config="mybatis.xml";
//2.读取主配置文件。使用mybatis框架中的Resources类
InputStream inputStream = Resources.getResourceAsStream(config);
//3.创建SqlSessionFactory对象, 使用SqlSessionFactoryBuidler类
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);

//4.获取SqlSession对象。
SqlSession session = factory.openSession();

//5.指定要执行的sql语句的 id
// sql的id = namespace+"."+ select|update|insert|delete标签的id属性值
String sqlId = "com.learn.dao.StudentDao"+"."+"selectStudentById";


// 6.通过SqlSession的方法,执行sql语句
Student student = session.selectOne(sqlId,1);
System.out.println("使用mybatis查询一个学生:"+student);

// 7.关闭SqlSession对象
session.close();
}
}

插入 insert

增加StudentDao接口

1
2
3
4
5
6
/**
* 插入一个学生信息
* @param student
* @return 返回受影响的行数,>0说明成功
*/
int insertStudent(Student student);

增加StudentDao.xml语句

1
2
3
<insert id="insertStudent">
insert into student values (#{id},#{name},#{email},#{age})
</insert>
  • 没有返回值,所以不需要resultType
  • 传参是一个Student实体,占位符使用实体的属性名称。

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Test
public void insertStudent() throws IOException {
String config = "mybatis.xml";
InputStream stream = Resources.getResourceAsStream(config);
SqlSessionFactory fa = new SqlSessionFactoryBuilder().build(stream);

// 获取一个session对象
SqlSession session = fa.openSession();

String sqlId = "com.learn.dao.StudentDao"+"."+"insertStudent";

Student student = new Student();
student.setId(4);
student.setName("小ing");
student.setEmail("ming@qq.com");
student.setAge(12);
int i = session.insert(sqlId, student);

System.out.println("受影响的行数为:"+i);

// 提交事务,mybatis默认是手动提交
session.commit();

session.close();
}
  • commit那一步是必须的,否则数据库插入不会成功。

其他

  • 实际上Dao的Interface文件都不是必须的,只需要有xml文件即可、

Mybatis代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* String sqlId="com.bjpowernode.dao.StudentDao.selectById";
* Student student = sqlSession.selectOne(sqlId,id);
*
*
* 测试方法中:调用dao的方法
* Student student = dao.selectById(1002);
*
* 1)dao: 通过反射能够得到 全限定类型名称
* dao是StudentDao类型的, 全限定名称 com.bjpowernode.dao.StudentDao
* 2)selectById: dao中的方法名称, 方法名称就是 mapper文件中标签的id
* 通过dao.selectById()能得到 sqlId="com.bjpowernode.dao.StudentDao.selectById";
*
* 3)确定调用SqlSession的那个方法
* 1.根据dao接口的方法返回中, 如果返回是一个对象,例如Student ,调用SqlSession.selectOne();
* 如果dao接口中的方法返回List, 调用SqlSession的 selectList();
*
* 2.根据mapper文件中的标签,如果标签是<insert>,调用SqlSession.insert()方法
*
*
* mybatis框架,发现使用dao的方法调用能确定 执行sql语句的必要信息, mybatis简化dao对象的实现。
* 由mybatis框架在程序执行期间, 根据你的Dao接口, 创建一个内存中的 接口的实现类对象。
* mybatis把这个技术叫做 dao技术(动态代理,dao的动态代理)。
*
* dao代理技术: 由mybatis创建 StudentDao接口的实现类 Proxy(StudentDaoImpl)
* 使用框架创建的StudentDaoImpl代替 "你自己手工实现的StudentDaoImpl类的功能",
* 不用开发人员写 dao接口的实现类。
*
* 使用dao的代理要求:
* 1. mapper文件中的namespace: 必须是dao接口的全限定名称
* 2. mapper文件中标签的id是 dao接口中的方法名称(一摸一样的)
*/

增加util工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.learn.util;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

public class MyBatisUtil {

private static SqlSessionFactory factory = null;

static {
String config = "mybatis.xml";
try {
InputStream stream = Resources.getResourceAsStream(config);
factory = new SqlSessionFactoryBuilder().build(stream);
} catch (IOException e) {
e.printStackTrace();
}
}

/**
* 获取SqlSession对象
* @return
*/
public static SqlSession getSqlSession(){
SqlSession session = null;
if(factory != null){
session = factory.openSession(true);// 自动提交事务
}
return session;
}
}

构造StudentDaoImpl实现类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.learn.dao.impl;

import com.learn.dao.StudentDao;
import com.learn.domain.Student;
import com.learn.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;

import java.util.List;

/**
* 通过实现Dao接口来达到传统的Dao的效果
*/
public class StudentDaoImpl implements StudentDao {

/**
* 通过session来执行sql语句获取对象
* @param id
* @return
*/
@Override
public Student queryStudentById(Integer id) {
SqlSession session = MyBatisUtil.getSqlSession();
String sqlId = "com.learn.dao.StudentDao.queryStudentById";
Student student = session.selectOne(sqlId, id);
session.close();
return student;
}

@Override
public List<Student> queryAllStudent() {
return null;
}
}

三种实现方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.learn;

import com.learn.dao.StudentDao;
import com.learn.dao.impl.StudentDaoImpl;
import com.learn.domain.Student;
import com.learn.util.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

public class MyTest {

/**
* 最古老的写法,直接调用SqlSession来执行sql语句
*/
@Test
public void testQueryStudentById(){
SqlSession session = MyBatisUtil.getSqlSession();
String sqlId = "com.learn.dao.StudentDao.queryStudentById";
Student student = session.selectOne(sqlId,1);
System.out.println(student);
session.close();
}

/**
* 使用传统的dao层写法
* dao的接口被一个实现类实现
* 通过 dao 对象 = new dao实现类();
* 最终直接获取结果
*/
@Test
public void testQueryStudentByIdWithDao(){
StudentDao dao = new StudentDaoImpl();
Student student = dao.queryStudentById(1);
System.out.println(student);
}


/**
* 通过Mybatis的代理技术来完成
* 通过调用getMapper方法来创建代理
* 要求:namespace是全路径名,sql的id是对应的方法名字
* 这样就不需要创建对应的impl实现类了
*/
@Test
public void testQueryStudentByIdWithProxy(){
SqlSession session = MyBatisUtil.getSqlSession();
StudentDao dao = session.getMapper(StudentDao.class);
Student student = dao.queryStudentById(1);
System.out.println(student);
session.close();
}
}

参数传递

parameterType

dao接口

1
2
3
4
5
6
7
 /**
* 根据id查询用户
* @param id
* @return
*/
Student queryStudentById(Integer id);

xml

1
2
3
4
<!-- 根据id查找student信息 -->
<select id="queryStudentById" parameterType="integer" resultType="com.learn.domain.Student">
SELECT id,name,email,age FROM student WHERE id=#{id}
</select>

test

1
2
3
4
5
6
7
8
9
10
/**
* xml文件中使用parameterType来指定类型,
* 不过Mybatis可以通过反射获取类型,可以省略不写
*/
@Test
public void testParameterType(){
StudentDao dao = MyBatisUtil.getSqlSession().getMapper(StudentDao.class);
Student student = dao.queryStudentById(1);
System.out.println(student);
}

仅一个简单参数

简单参数:java基本数据类型和String类型。

占位符#{xxx},内容随意指定就好。

多个简单参数

dao

1
2
3
4
5
6
/**
* 根据用户名或者年龄查询学生
* @return
*/
List<Student> queryStudentByNameOrAge(@Param(value = "stuName") String name,
@Param("stuAge") Integer age);

xml

1
2
3
4
<!-- 根据年龄或者用户名查找信息 -->
<select id="queryStudentByNameOrAge" resultType="com.learn.domain.Student">
SELECT id,name,email,age FROM student WHERE name=#{stuName} OR age = #{stuAge}
</select>

test

1
2
3
4
5
6
7
8
9
10
11
/**
* 对于含有多个简单参数的查询
* 使用 @Param() 注解来定义参数名
* 然后在xml中使用对应的参数名#{参数名}来作为占位符
*/
@Test
public void testMultSimpleParam(){
StudentDao dao = MyBatisUtil.getSqlSession().getMapper(StudentDao.class);
List<Student> studentList = dao.queryStudentByNameOrAge("蓝", 23);
studentList.forEach(item -> System.out.println(item));
}

参数是一个对象

新建vo.RequestVo.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package com.learn.vo;

/**
* @author jianfengjf.liu
* @date 2021/8/4 19:17
*/
public class RequestVo {
private Object p1;
private Object p2;

public Object getP1() {
return p1;
}

public void setP1(Object p1) {
this.p1 = p1;
}

public Object getP2() {
return p2;
}

public void setP2(Object p2) {
this.p2 = p2;
}

@Override
public String toString() {
return "RequestVo{" +
"p1=" + p1 +
", p2=" + p2 +
'}';
}
}

dao

1
2
3
4
5
6
/**
* 根据传递的一个对象iang进行查询
* @param obj
* @return
*/
List<Student> queryStudentByObject(RequestVo obj);

xml

1
2
3
4
<!-- 根据传入的对象来进行查询 -->
<select id="queryStudentByObject" parameterType="com.learn.vo.RequestVo" resultType="com.learn.domain.Student">
SELECT id,name,email,age FROM student WHERE name =#{p1} OR email = #{p2}
</select>

test

1
2
3
4
5
6
7
8
9
@Test
public void testSelectByObject(){
StudentDao dao = MyBatisUtil.getSqlSession().getMapper(StudentDao.class);
RequestVo obj = new RequestVo();
obj.setP1("蓝");
obj.setP2("xiaoming@qq.com");
List<Student> studentList = dao.queryStudentByObject(obj);
studentList.forEach(item -> System.out.println(item));
}

枚举

mybatis中枚举的处理方案,定义接口enum,然后编写接口对应的TypeHandler,然后在xml里映射


问题

当查询结果为一个List但是却写成了一个单独的对象时会怎么样?

  • 报异常
1
2
3
org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 2

at org.apache.ibatis.session.defaults.DefaultSqlSession.selectOne(DefaultSqlSession.java:80)

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!