Mybatis学习笔记
注:因为本人第一次学习mybatis,这个文章只是想记录一下学习的总结笔记,如果理解有偏差欢迎指正,谢谢包含;
一、Mybatis概述
基本概念
百度百科:
MyBatis本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。2013年11月迁移到Github。
iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAOs)。
特点:
- 简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件。易于学习,易于使用。通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
- 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
- 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
- 提供映射标签,支持对象与数据库的orm字段关系映射。
- 提供对象关系映射标签,支持对象关系组建维护。
- 提供xml标签,支持编写动态sql。
三层架构
一般一个bs项目通常分为三层:界面层(User Interface layer)、业务逻辑层(Business Logic Layer)、数据访问层(Data access layer)
MyBatis就是三层架构中的数据访问层所使用的框架;
学习Mybatis前先简单介绍一下这三部分:
三层的职责 :
- 界面层(表示层,视图层):主要功能是接受用户的数据,显示请求的处理结果。使用 web 页面和用户交互,手机 app 也就是表示层的,用户在 app 中操作,业务逻辑在服务器端处理。
- 业务逻辑层:接收表示传递过来的数据,检查数据,计算业务逻辑,调用数据访问层获取数据。
- 数据访问层:与数据库打交道。主要实现对数据的增、删、改、查。将存储在数据库中的数据提交给业务层,同时将业务层处理的数据保存到数据库.
优点:
三层架构区分层次的目的是为了 “高内聚,低耦合”。开发人员分工更明确,将精力更专注于应用系统核心业务逻辑的分析、设计和开发,加快项目的进度,提高了开发效率,有利于项目的更新和维护工作。
三层对应的包:
界面层: controller包 (servlet)
业务逻辑层: service 包(XXXService类)
数据访问层: dao包(XXXDao类)
三层中类的交互:
用户使用界面层–> 业务逻辑层—>数据访问层(持久层)–>数据库(mysql)
三层对应的处理框架:
界面层—servlet—springmvc(框架)
业务逻辑层—service类–spring(框架)
数据访问层—dao类–mybatis(框架)
简单了解了这三层架构,那么学习MyBatis过程中就更能清楚的知道到自己学的框架到底干什么,用在哪里了;
自我理解
Mybatis就是一个可以自定义Sql的持久层框架,提供了操作数据库的能力;它内置了 JDBC 操作,简化了数据库的访问流程,让我们只需要关心如何写好Sql语句即可;(可以简单理解为就是一个增强的JDBC)
JDBC回顾
简单回顾一下JDBC的主要五部操作:
// 1,注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2,获取链接
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/数据库名?serverTimezone=UTC", "用户", "密码");
// 3,获取数据操作对象
String sql = "sql语句";
preparedStatement = connection.prepareStatement(sql);
// 4,执行sql语句
resultSet = preparedStatement.executeQuery();
// 5,处理结果集
while (resultSet.next()) {
// 对应操作
}
这五步只要你需要写sql语句操作数据库都需要写,所以很麻烦,于是我们又自定义了JDBC工具类:
/*
JDBC工具类,简化JDBC编程
*/
public class DBUtil {
/**
* 工具类中的构造方法是私有的
* 因为工具类中的方法都是静态的,直接通过类名去调即可。
*/
private DBUtil(){}
/**
* 静态代码块,类加载的时候执行
* 把 注册驱动 程序的代码放在静态代码块中,避免多次获取连接对象时重复调用
*/
static {
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
// 获取连接
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection("jdbc:mysql://localhost:3306/数据库名?serverTimezone=UTC", "用户", "密码");
}
// 关闭方法
public static void close(Connection connection, Statement statement, ResultSet resultSet) {
if (resultSet != null) {
try {
resultSet.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
这样大大减少了重复代码的出现,但是每次使用时依然不够精简,因为处理结果等操作依旧是重复的;
并且最不好的一点就是Sql语句和Java代码写在了一起,如果想要快速找到某个Sql语句的操作十分麻烦,而且不易管理;
所以为了解决这个问题,我们可以使用MyBatis来写出更简洁的代码来;
MyBatis解决的问题
减轻使用 JDBC 的复杂性,不用编写重复的创建 Connetion , Statement ;不用编写关闭资源代码; 可以直接使用 java 对象表示结果数据,让开发者专注 SQL 的处理,其他分心的工作由 MyBatis 代劳。
所以上面的JDBC的操作MyBatis都可以解决,而需要我们做的就是写好Sql语句配置好就可以了;
二、第一个MyBatis程序
前提条件
现在我已经在数据库中创建好了一个student表
接下来的所有操作都是基于这张表进行;
准备工作
在Maven中添加对应版本的Mybatis依赖来导入jar包
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
最好把mysql的依赖也加上
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
在 < build >标签中加上以下内容:
<resources>
<resource>
<!--所在的目录-->
<directory>src/main/java</directory>
<!--包括目录下的.properties,.xml 文件都会扫描到-->
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
为什么需要这个呢?这是为了保证编译后对应的xml等配置文件也可以生成到对应的编译后的文件(target)位置,这样Mybatis才可以从中找到我们的配置;如图:
项目结构
配置好了maven对应的pom.xml文件后,接下来就需要构建项目了,这里我先展示一下整体的结构,因为每一部分的内容都是固定的,所以一定要清楚每个位置应该放什么东西
可以看到我创建了一个Maven模块,创建了一个com.yang包,这个包下有三个包,下面分别介绍一下它们是干什么的
- dao包:这个包是放接口和对应的Mapper.xml文件的,在这里mapper文件名必须和对应接口名相同;
- domain包:这个包存放对应数据库表的一些类
- utils包:顾名思义这是个工具包;
下面的resource包用来存放config.xml等一些配置文件;
test包就是一个测试用的包;
记住这些结构,它们其实都是固定的,后面我会一一说;
domain包
这个包中的类对应数据库的每一个表,后期可以通过Mybatis来执行sql获取对应表类型的数据;
因为我只有一个student表,所以需要一个Student类,这个类的属性需要和student表的每一个字段类型相匹配,并且建议最好属性名和student表的属性名相同,不然后期会多几步操作(后面会讲)
public class Student {
private int id;
private String name;
private String email;
private int age;
public Student() {
}
public Student(int id, String name, String email, int age) {
this.id = id;
this.name = name;
this.email = email;
this.age = age;
}
public int getId() {
return id;
}
public void setId(int 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 int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
", age=" + age +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Student)) return false;
Student student = (Student) o;
return id == student.id && age == student.age && Objects.equals(name, student.name) && Objects.equals(email, student.email);
}
@Override
public int hashCode() {
return Objects.hash(id, name, email, age);
}
}
这里需要注意:一定要把get和set方法加上,因为mybatis的实现设计到动态代理的内容,这里就不过多阐述了,但是这些细节一定要记住;
这里我重写了toString等方法,就是为了测试方便;
dao包
这个包放的是一些接口,这些接口的作用就是来写sql操作的;同样一个接口对应一个表
StudentDao接口:
package com.yang.dao;
import com.yang.domain.Student;
public interface StudentDao {
// 查询方法
List<Student> selectStudent(); // 查询结构返回一个Student的list集合
}
通俗点说就是:一个接口对应一个表的sql操作,接口中的一个方法对应执行一条sql语句;
这个包下同样还有mapper.xml文件,这个文件是用来定位接口中的对应方法,来写sql语句的,并且一个类型的接口对应一个mapper.xml文件;
注意mapper.xml文件要和对应的接口名相同
StudentDao.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">
<!--
结论:上面是固定格式,直接复制粘贴就可以;
sql映射文件:写sql语句,mybatis会执行;
mybatis-3-mapper.dtd是约束文件名称;
约束文件作用:限制、检查在当前文件中出现的标签、属性是否符合mybatis规范;
-->
<!--
mapper是当前文件的根标签;
namespace:命名标签,唯一值,可以是自定义的字符串,
但是这里要求使用dao接口的全限名称
-->
<mapper namespace="com.yang.dao.StudentDao">
<!--
mapper内部可以通过以下标签表示数据库的操作:
<select>:查询
<update>:更新
<insert>:插入(新增)
<delete>:删除
-->
<!--
查询语句
id:执行sql语法的唯一标识,mybatis会通过这个id找到要执行的sql语句,但是要写成对应dao接口的 方法名(动态代理的要求)
resultType:表示结果类型,是sql执行后得到的ResultSet遍历得到的Java对象类型;
值为该类型的权限名称
-->
<select id="selectStudent" resultType="com.yang.domain.Student">
select * from student order by id
</select>
</mapper>
这个文件的结构解释都写在注释中了,除了select标签其余部分就是一个固定格式,直接用就行;
现在再理一下为什么这个包要放接口和mapper.xml文件:
因为这里就有一个student表,如果想要对该表进行数据操作,就需要一个StudentDao接口
对表进行一个操作(增删改查)就要在接口中写一个抽象方法(操作名)
想要写StudentDao接口中对应操作的sql语句就需要一个和接口同名的mapper.xml文件:StudentDao.xml
最后在StudentDao.xml中写StudentDao接口中的抽象方法的sql语句(查询 、更新、新增、删除)
因为这一块不好描述清楚,所以我只能按照我的理解来描述,可能会有人疑惑为什么必须要写个StudentDao接口?写成抽象类不行吗?为什么id什么的必须相对应?这就涉及到了mybatis的底层实现了,这是动态代理所要求的,必须写接口,必须这样写,实在疑惑的话可以看看代理模式的相关内容,你就会明白为什么了;
补充:dao接口中的方法不能重载,一旦出现重名的操作后Mybatis就无法判断执行哪一个了;
resource包
先跳过utils包,先说说resource包;
这个包下也有一个很重要的xml文件:config.xml
这个文件是 MyBatis 主配置文件,在dao包中的所有mapper.xml文件都整合到该文件中,且该文件中配置对应的数据库信息;
实际开发可能会有多个数据库,所以最好把每个数据库信息单独写在一个properties文件中,由config.xml文件读取;
jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/数据库名?serverTimezone=UTC
jdbc.username=用户
jdbc.password=密码
jdbc.driver.online=com.mysql.cj.jdbc.Driver
jdbc.url.online=jdbc:mysql://localhost:3306/数据库名?serverTimezone=UTC
jdbc.username.online=用户
jdbc.password.online=密码
mybatis.xml
<?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>
<!--设置jdbc连接信息的配置文件jdbc.properties路径(该声明必须放在上面)-->
<properties resource="jdbc.properties" />
<!--settings:控制mybatis全局行为-->
<settings>
<!--设置mybatis输出日志-->
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
<!--环境配置: 数据库的连接信息
default:必须和某个environment的id值一样。
告诉mybatis使用哪个数据库的连接信息。也就是访问哪个数据库
-->
<environments default="mydev">
<!-- environment : 一个数据库信息的配置, 环境
id:一个唯一值,自定义,表示环境的名称。
-->
<environment id="mydev">
<!--
transactionManager :mybatis的事务类型
type: JDBC(表示使用jdbc中的Connection对象的commit,rollback做事务处理)
-->
<transactionManager type="JDBC"/>
<!--
dataSource:表示数据源,连接数据库的
type:表示数据源的类型, POOLED表示使用连接池
-->
<dataSource type="POOLED">
<!--
driver, user, username, password 是固定的,不能自定义。
-->
<!--数据库的驱动类名-->
<property name="driver" value="${jdbc.driver}"/>
<!--连接数据库的url字符串-->
<property name="url" value="${jdbc.url}"/>
<!--访问数据库的用户名-->
<property name="username" value="${jdbc.username}"/>
<!--密码-->
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
<!--下面这个environment标签内的内容是为了和上面对比写的,在这个例子中不需要的-->
<!--表示线上的数据库,是项目真实使用的库-->
<environment id="online">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver.online}"/>
<property name="url" value="${jdbc.url.online}"/>
<property name="username" value="${jdbc.username.online}"/>
<property name="password" value="${jdbc.password.online}"/>
</dataSource>
</environment>
</environments>
<!-- sql mapper(sql映射文件)的位置-->
<mappers>
<!--一个mapper标签指定一个文件的位置。
从类路径开始的路径信息。 target/clasess(类路径)
-->
<mapper resource="com/yang/dao/StudentDao.xml"/>
</mappers>
</configuration>
<!--
mybatis的主配置文件: 主要定义了数据库的配置信息, sql映射文件的位置
1. 约束文件
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
mybatis-3-config.dtd:约束文件的名称
2. configuration 根标签。
-->
mapper标签就是对应的dao包中的StudentDao.xml文件,dao包中有几个xml文件就写几个mapper标签;
设置输出日志
其中有一段代码:
<settings>
<!--设置mybatis输出日志-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
这样在测试时可以输出日志信息,好处就是如果出现了错误可以从日志中很快的找出来;所以建议加上
设置properties文件
这里还要说一点,在xml文件中引入properties文件的操作中,首先要加一个:
<!--设置jdbc连接信息的配置文件jdbc.properties路径(该声明必须放在上面)-->
<properties resource="文件名.properties" />
<!--properties文件和xml文件要放在一起-->
然后在下面调用properties文件内容的时候value就是这样写:
value="${key值}"
记住就行;
小测试
做完这些就可以写一个测试代码测试了,我们写好了sql语句,只需要执行sql语句对应的StudentDao中的方法就可以了;
测试代码:
@Test
public void selectStudent() throws IOException {
// 访问mybatis读取student数据
// 1.定义mybatis主配置文件的名称, 从类路径的根开始(target/clasess)
String config = "mybatis.xml";
// 2.读取这个config表示的文件
InputStream in = Resources.getResourceAsStream(config);
// 3.创建了SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 4.创建SqlSessionFactory对象,build()方法
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(in);
// 5.获取SqlSession对象,从SqlSessionFactory中获取SqlSession
SqlSession session = sqlSessionFactory.openSession();
// 6.使用mybatis的动态代理机制, 使用SqlSession.getMapper(dao接口)
// getMapper能获取dao接口对于的实现类对象
StudentDao studentDao = session.getMapper(StudentDao.class);
// 7.执行sql语句(其实执行的是sql语句对应的方法)
List<Student> students = studentDao.selectStudent();
// 8.输出结果
students.forEach(stu -> System.out.println(stu)); // Lambda表达式(直接遍历也行)
// 9.关闭SqlSession对象
session.close();
}
输出结果:
Student{id=1001, name='张三', email='zhangsan@123.com', age=18}
Student{id=1002, name='李四', email='lisi@123.com', age=28}
Student{id=1003, name='王五', email='wangwu@123.com', age=38}
Student{id=1006, name='小六', email='xiaoliu@123.com', age=66}
Student{id=1007, name='小七', email='xiaoqi@123.com', age=77}
Student{id=1008, name='小八', email='xiaoba@123.com', age=88}
Student{id=1009, name='九小', email='jiuxiao@123.com', age=99}
看到执行步骤这么多,是不是感觉还不如JDBC的那几句代码呢?
当实际开发中查询量大时使用这种方法还是方便的,毕竟还是省去好多代码并且对于sql语句的管理也更方便了;
简单介绍一下用到的几个方法:
Resources 类
Resources 类,顾名思义就是资源,用于读取资源文件。其有很多方法通过加载并解析资源文件,返回不同类型的 IO 流对象;
SqlSessionFactoryBuilder 类
SqlSessionFactory 的创建 ,需要使用 SqlSessionFactoryBuilder 对象的build()方法 ;由于 SqlSessionFactoryBuilder 对象在创建完工厂对象后,就完成了其历史使命,即可被销毁;所以,一般会将该 SqlSessionFactoryBuilder 对象创建为一个方法内的局部对象,方法结束,对象销毁;
SqlSessionFactory 接口
SqlSessionFactory 接口对象是一个重量级对象(系统开销大的对象),是线程安全的,所以一个应用只需要一个该对象即可(不要多次重复创建);创建 SqlSession 需要使用 SqlSessionFactory 接口的的 openSession()方法;
openSession(true):创建一个有自动提交功能的 SqlSession
openSession(false):创建一个非自动提交功能的 SqlSession,需手动提交
openSession():同 openSession(false)
SqlSession 接口
SqlSession 接口对象用于执行持久化操作;一个 SqlSession 对应着一次数据库会话,一次会话以 SqlSession 对象的创建开始,以 SqlSession 对象的关闭结束; SqlSession 接口对象是线程不安全的,所以每次数据库会话结束前,需要马上调用其 close()方法,将 其关闭;再次需要会话,再次创建;SqlSession 在方法内部创建,使用完毕后关闭;
SqlSession 的 getMapper(Class class)方法,可获取指定接口的实现类对象。该方法的参数为指定 Dao 接口类的 Class 值;
虽然说Mybatis帮我们省去了大部分步骤,但是每次都需要写这几句代码也是有点麻烦,不如直接把它们封装成一个工具类;
utils包
终于就剩最后一个包了,我们在这个包中来声明各种工具类,为了简化Mybatis执行代码步骤,我们封装一个MybatisUtil工具类;
MybatisUtil.java
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;
// 工具类,返回一个SqlSession对象
public abstract class MybatisUtil {
private static SqlSessionFactory sqlSessionFactory = null;
static {
String config = "mybatis.xml";
try {
InputStream in = Resources.getResourceAsStream(config);
// 创建SqlSessionFactory对象,且只创建一次,因为它开销大,将用于整个应用,
// 放在静态代码块中只在类初始化中创建一次
sqlSessionFactory = new SqlSessionFactoryBuilder().build(in);
} catch (IOException e) {
e.printStackTrace();
}
}
// 获取SqlSession对象
public static SqlSession getSqlSession() {
SqlSession sqlSession = null;
if(sqlSessionFactory != null){
sqlSession = sqlSessionFactory.openSession();// 非自动提交事务
}
return sqlSession;
}
}
这样通过这个工具类就可以获取到SqlSession对象了;工具类写成抽象类也是为了防止使用工具类创建对象;
再来个小测试:
@Test
public void selectStudent() {
// 直接获取到SqlSession对象
SqlSession session = MybatisUtil.getSqlSession();
StudentDao studentDao = session.getMapper(StudentDao.class);
List<Student> students = studentDao.selectStudent();
students.forEach(stu -> System.out.println(stu));
session.close();
}
这样步骤就减少了很多了,多次执行sql语句时就不需要那么多步骤了;
补充
设置自动提交事务
因为这个例子是select查找语句,直接查找就可以了;但是如果是update、delete、insert的话就涉及事务的问题,在默认情况下事务提交是关闭的,所以在执行sql语句后想要在数据库中增添数据就要手动提交,就是多了一行代码,当执行完sql语句,在下面直接调用SqlSession对象的commit()
即可;
注:这里省略前面的一些代码,主要想展示一下如何提交数据;
插入insert语句的测试:
@Test
public void insertStudent() {
SqlSession session = MybatisUtil.getSqlSession();
StudentDao studentDao = session.getMapper(StudentDao.class);
int num = studentDao.insertStudent(new Student(1010, "小石" , "xiaoshi@123.com", 10));
session.commit(); // 手动提交事务,就是想说的这一点
session.close();
System.out.println("新增了" + num + "条数据");
}
输出结果(包括日志内容):
如果想要开始就设置自动提交事务呢?
这就需要改动MybatisUtil.java
工具类,也就是改一处即可:
sqlSession = sqlSessionFactory.openSession(true); // 自动提交事务
SqlSessionFactory 的 openSession() 方法分为有参数和无参数的,无参数默认就是关闭事务提交的,而有参数的将参数设置为true就开启事务自动提交了;
在测试代码中就不再需要写session.commit()
手动提交事务了;
输出结果:
package 一次配置所有mapper.xml
如果存在多个表时,dao包下就会有多个mapper.xml文件,那么主配置文件config.xml配置起来就很麻烦,这时我们可以使用package指定dao包下的所有mapper文件,一次就完成了所有mapper文件的配置;
语法: <package name=“对应包的路径”>
示例:
mybatis.xml
<!-- sql mapper(sql映射文件)的位置-->
<mappers>
<!--<mapper resource="com/yang/dao/StudentDao.xml"/>-->
<!--用package代替mapper,表示dao包下的所有mapper.xml文件-->
<package name="com.yang.dao"/>
</mappers>
注意:该方法要求 Dao 接口名称和 mapper 映射文件名称相同,且在同一个目录中;
三、MyBatis 传递参数
如果我们想要实现输入数据执行查询到结果,又或者输入一些数据实现在数据库的新增…这种情况下sql语句就不能写死,如何把传入java代码中的参数传给sql语句呢?下面就简单描述一下方法;
一个参数
当传入的是一个参数时,直接传给sql语句即可,注意sql语句中传入的变量为:#{参数名}
StudentDao.java
public interface StudentDao {
// 查询方法,从数据库中通过id查找对应学生
Student selectStudent03(int id); // 一个参数查询,返回值是Student类型
}
mapper文件:
StudentDao.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="com.yang.dao.StudentDao">
<!--一个参数,#{id}就是传入的参数-->
<select id="selectStudent03" resultType="com.yang.domain.Student">
select * from student where id = #{id}
</select>
</mapper>
config文件:
mybatis.xml
<?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>
<!--设置jdbc连接信息的配置文件jdbc.properties路径(该声明必须放在上面)-->
<properties resource="jdbc.properties" />
<!--settings:控制mybatis全局行为-->
<settings>
<!--设置mybatis输出日志-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<environments default="test">
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--数据库的驱动类名-->
<property name="driver" value="${jdbc.driver}"/>
<!--连接数据库的url字符串-->
<property name="url" value="${jdbc.url}"/>
<!--访问数据库的用户名-->
<property name="username" value="${jdbc.username}"/>
<!--密码-->
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!-- sql mapper(sql映射文件)的位置-->
<mappers>
<mapper resource="com/yang/dao/StudentDao.xml"/>
</mappers>
</configuration>
jdbc.properties略;
测试代码:
@Test
public void selectStudent07() {
SqlSession session = MybatisUtil.getSqlSession();
StudentDao studentDao = session.getMapper(StudentDao.class);
Student student = studentDao.selectStudent03(1003); // 从数据库中查找id为1003的学生
session.close();
System.out.println(student);
}
输出结果:
通过输出结果以及打印的日志信息可以看到整个sql语句的执行过程;
传递多参数
传递多参数就麻烦一些了,但是并不难,因为这篇文章我已经写过了,直接开个传送门过去吧
#和$
在参数传递时经常看到 ‘ # ’ 符号,其实还有一个 ‘ $ ’ 符号,下面来对比一下这两种符号代表的意思;
#:占位符,告诉 mybatis 使用实际的参数值代替;并使用 PrepareStatement 对象执行 sql 语句, #{…} 代替 sql 语句的占位符“?”。这样做更安全(可以防止sql注入),更迅速,通常也是首选做法;
$:字符串替换,告诉 mybatis 使用 $ 包含的“字符串”替换所在位置;使用 Statement 把 sql 语句和${…}的 内容连接起来(就是字符串拼接,直接把sql语句和传入的内容拼起来)。主要用在替换表名,列名,不同列排序等操作;但是Statement存在Sql注入问题,所以一般不建议使用;(并不是没用,分页查询还是用得到的)
#和$的区别:
- #使用 ’ ? ’ 在sql语句中做占位符, 使用PreparedStatement执行sql,效率高
- #能够避免sql注入,更安全。
- $不使用占位符,是字符串连接方式,使用Statement对象执行sql,效率低
- $有sql注入的风险,缺乏安全性。
- $:可以替换表名或者列名
四、属性名和字段名不同解决方法
resultMap
前面我说过尽可能保证domain包下的Student类的属性名和student表中的字段名相同,但是在实际开发中总会存在例外,如果出现了属性名和字段名不同的情况,同样也有解决办法,就是使用resultMap元素
resultMap 可以自定义 sql 的结果和 java 对象属性的映射关系,更灵活的把列值赋值给指定属性;常用在列名和 java 对象属性名不一样的情况;
使用方式:
1.先定义 resultMap,指定列名和属性的对应关系;
2.在中把 resultType 替换为 resultMap;
代码演示
将domain中的Student类的属性稍作修改:
public class Student {
// 这次的属性名和表的字段名不同
private int myId;
private String myName;
private String myEmail;
private int myAge;
// 下面还是相同的一系列get、set操作,就不写了
}
mapper.xml文件:
StudentDoa.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="com.yang.dao.StudentDao">
<!--
当数据库表的列名和java程序的属性名 不一样 时记住要用resultMap,
id值就是整个resultMap的自定义的值,用来和下面的查询语句匹配的
type就是resultMap匹配的类型
-->
<resultMap id="studentMap" type="com.yang.domain.Student">
<!--column: 是数据库中表的列名 property:在这里就是Student类中的属性名-->
<!-- 主键字段使用 id标签 -->
<id column="id" property="myId" />
<!--非主键字段使用 result标签-->
<result column="name" property="myName" />
<result column="email" property="myEmail" />
<result column="age" property="myAge" />
</resultMap>
<!--select标签中的 resultMap="studentMap"就是对应id的resultMap-->
<select id="selectStudent02" resultMap="studentMap">
select * from student where id > #{myId}
</select>
</mapper>
其他剩余操作就不演示了,和前面都一样,这里主要想强调的还是 <resultMap> 标签,通过它来把不同的属性名和字段名联系起来;
resultType
说到resultMap了也顺带聊聊resultType,其实上面第一个示例也介绍过了,这里再提一下;
resultType: 执行 sql 得到 ResultSet 转换的类型,使用类型的完全限定名或别名; 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身;
要注意resultType 和 resultMap不能同时使用;
五、Like模糊查询
因为sql中的like模糊查询需要加 ‘ %’ 号,所以就在这里说一下两种实现:
- 在mapper文件的sql语句中写 ‘ %’ 号
- 在java代码中传入一个带 ‘ %’ 号的字符串给sql语句
下面简单演示一下:
在sql语句中写 ‘ %’ 号
StudentDao.java
List<Student> selectStudentByLike(@Param("likeName") String name); // Like测试
StudentDao.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="com.yang.dao.StudentDao">
<!--在sql语句中写%-->
<select id="selectStudentByLike" resultType="com.yang.domain.Student">
select * from student where name like %#{likeName}%
</select>
</mapper>
测试代码:
@Test
public void selectStudent03() {
SqlSession session = MybatisUtil.getSqlSession();
StudentDao studentDao = session.getMapper(StudentDao.class);
// like模糊查询,传入查询内容
List<Student> students = studentDao.selectStudentByLike("小");
session.close();
students.forEach(stu -> System.out.println(stu));
}
这种方法其实并不建议,因为模糊查询可以分为三种查询方式:%内容、%内容%、内容%
所以这样写你就无法自己想怎么差就怎么查了,想换种查询方法还得修改sql语句;所以不建议使用该方法;
java代码传入“%查询内容%”
这种方法的灵活度就高了,因为java代码传入的就是查询方式和内容,下面演示一下:
StudentDao.java
List<Student> selectStudentByLike(@Param("likeName") String name); // Like测试
StudentDao.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="com.yang.dao.StudentDao">
<!--sql语句直接传一个字符串-->
<select id="selectStudentByLike" resultType="com.yang.domain.Student">
select * from student where name like #{likeName}
</select>
</mapper>
测试代码:
@Test
public void selectStudent03() {
SqlSession session = MybatisUtil.getSqlSession();
StudentDao studentDao = session.getMapper(StudentDao.class);
// like模糊查询,直接传入拼接的字符串,这样的灵活度更高
List<Student> students = studentDao.selectStudentByLike("%小%");
session.close();
students.forEach(stu -> System.out.println(stu));
}
使用这种方法就可以了,上面第一种方法了解一下就行;
六、动态 SQL
官方定义
动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。
下面介绍四个常用的标签:
- if
- where
- set
- foreach
<if>标签
语法: <if test=“条件”> sql 语句 <if>
规则: 如果test中的条件为true时,才会把 if 标签中的sql语句拼接到前面的sql语句上;
使用示例:
StudendDao.java
List<Student> selectStudentIf(Student student); // If标签
StudentDao.xml
<!--if标签-->
<select id="selectStudentIf" resultType="com.yang.domain.Student">
select * from student where id > 0
<if test="name != null and name != '' ">
and id > #{id}
</if>
<if test="age >= 0">
and age > #{age}
</if>
</select>
简单解释一下,如果"name != null and name != ''
成立,那么sql语句就是:select * from student where id > 0 and id > #{id}
如果age >= 0
也成立,sql语句为:select * from student where id > 0 and id > #{id} and age > #{age}
如果只有age >= 0
成立,那么sql语句为:select * from student where id > 0 and age > #{age}
就是满足条件就拼接,不满足就不管;
测试代码:
@Test
public void selectStudent04() {
SqlSession session = MybatisUtil.getSqlSession();
StudentDao studentDao = session.getMapper(StudentDao.class);
List<Student> students = studentDao.selectStudentIf(new Student(1006, "小六" , "xiaoliu@123.com", 66));
session.close();
students.forEach(stu -> System.out.println(stu));
}
输出结果:
可以看到if标签中所有条件都满足,所以都拼接上了;
<where>标签
if 标签存在一个问题:你可以发现在上面的 if 标签例子中,where语句后面开始就加了一个 id>0
,为什么呢?假设没有这段sql语句,那么开始时我的sql语句就是:select * from student where
;第一个条件满足还好说,拼接后就是:select * from student where id > #{id}
,那么如果第一个条件不满足第二个条件满足呢?拼接后就成了:select * from student where and age > #{age}
,你就会发现这个sql语句是错误的,where后面怎么能直接跟一个and;如果两个条件都不满足那么sql语句不就成了:select * from student where
这样了吗?
这样就会造成sql语法的错误,所以我在前面加上了一个不论何时都满足的条件:id>0
;
但是这并不是最优方法,如果查询数据量大时这样很影响效率,这个时候where标签就可以解决这个问题;
where标签语法: <where> 其他动态 sql语句(比如if语句) </where>
规则: where 标签只会在子标签返回任何内容的情况下才插入 “WHERE” 子句;而且,若子句的开头为 “AND” 或 “OR”,where 标签也会将它们去除;
使用示例:
StudendDao.java
List<Student> selectStudentWhere(Student student); // Where标签
StudentDao.xml
<!--where标签-->
<select id="selectStudentWhere" resultType="com.yang.domain.Student">
select * from student
<where>
<if test="name != null and name != '' ">
id > #{id}
</if>
<if test="age >= 0">
and age > #{age}
</if>
</where>
</select>
和if标签都一个意思,但是这里sql语句中的where
就不用我们自己写了,如果where标签中有满足条件的就拼接上,都不满足where语句就不会存在;
测试代码:
@Test
public void selectStudent05() {
SqlSession session = MybatisUtil.getSqlSession();
StudentDao studentDao = session.getMapper(StudentDao.class);
List<Student> students = studentDao.selectStudentWhere(new Student(1006, "小六" , "xiaoliu@123.com", 66));
session.close();
students.forEach(stu -> System.out.println(stu));
}
输出结果:
对于where标签和if标签的选择应该根据实际情况,知道用法灵活变通;
<set>标签
set标签和where标签作用差不多,但是set标签就会用在update语句中,set 标签可以用于动态包含需要更新的列,忽略其它不更新的列;
语法:<set>动态sql语句</set>
规则: set标签也是只有子标签返回任何内容时才拼接,就是和where标签使用的位置不同罢了;(但是并不意味着update不能使用where标签了)
使用示例:
StudendDao.java
int updateStudent02(Student student); // set标签
StudentDao.xml
<!--set标签专门用于更新语句的,和where标签的作用相似,只是使用位置不同-->
<update id="updateStudent02">
update student
<set>
<if test="id != null">id = #{id},</if>
<if test="name != null">name = #{name},</if>
<if test="email != null">email = #{email},</if>
<if test="age != null">age = #{age}</if>
</set>
where id = #{id}
</update>
测试代码:
@Test
public void updateStudent02() {
SqlSession session = MybatisUtil.getSqlSession();
StudentDao studentDao = session.getMapper(StudentDao.class);
int num = studentDao.updateStudent02(new Student(1009, "小九" , "xiaojiu@123.com", 9));
session.commit(); // 提交事务(默认是关闭的,所以需要手动提交)
session.close();
System.out.println("更新了" + num + "条数据");
}
输出结果:
<foreach>标签
动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候),这时候就可以使用foreach标签;
语法:
<foreach collection=“集合类型” item=“集合中的成员” open=“开始字符” close=“结束字符” separator=“集合成员间的分隔符”>
#{item值}
</foreach>
规则: 可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach;当使用 Map 对象(或者 Map.Entry 对象的集合)时,需要在foreach中额外加一个index属性,index 是键,item 是值;
使用示例:
StudendDao.java
List<Student> selectStudentFor(List<Student> student); // foreach标签
StudentDao.xml
<!--foreach标签-->
<!--
当存在一个集合时查找该集合元素可以使用
collection:所要查找的集合类型
item:对应方法中形参的名字(即接口中方法形参名:List<Student> student)
open:循环开始前家的东西
close:循环结束后加的东西
separator:集合中每一个元素的分隔符
-->
<!--注意这里sql语句后面使用了in()-->
<select id="selectStudentFor" resultType="com.yang.domain.Student">
select * from student where id in
<foreach collection="list" item="student" open="(" close=")" separator=",">
#{student.id}
</foreach>
</select>
其实这个sql语句可以简单抽象成这样:
select * from student where id in (
#{student.id}
)
<!--因为student是一个list集合,所以通过for循环每次从中遍历id值,每生成一个id后面再加一个分隔符‘,’-->
所以最后sql语句可以拼接成:select * from student where id in (student.id01, student.id02, student.id03.....)
测试代码:
@Test
public void selectStudent06() {
SqlSession session = MybatisUtil.getSqlSession();
StudentDao studentDao = session.getMapper(StudentDao.class);
List<Student> student = new ArrayList<>();
student.add(new Student(1006, "小六" , "xiaoliu@123.com", 66));
student.add(new Student(1007, "小七" , "xiaoqi@123.com", 77));
student.add(new Student(1009, "小九" , "xiaojiu@123.com", 9));
List<Student> students = studentDao.selectStudentFor(student);
session.close();
students.forEach(stu -> System.out.println(stu));
}
输出结果:
array、set集合和list都是一样的,map就多了一个index属性这一点区别,这里就不示范了;
七、小拓展
使用PageHelper实现分页功能
项目开发中分页功能几乎是每次都会出现的,一般我们会通过limit进行操作:
select * from 表名 limit (pageNum - 1)*pageSize, pageSize
pageNum是当前的页数,pageSize是每页显示的记录条数,这个公式可以自己找找规律推导一下;
但是这只是关键步骤,实际情况下还是很麻烦,所以就有牛人写了一个mybatis分页插件,PageHelper;
这个插件在GitHub上可以搜到:传送门
引入jar包
可以直接使用Maven依赖来引入jar包:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>最新版本</version>
</dependency>
添加plugin 配置
在config.xml文件中还需要添加额外的配置,需要加到<environments>
标签之前;
mybatis.xml
<!--添加PageHelper的配置-->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor" />
</plugins>
配置文件
mapper文件:
StudentDao.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="com.yang.dao.StudentDao">
<select id="selectStudent" resultType="com.yang.domain.Student">
select * from student
</select>
</mapper>
config文件
mybatis.xml
<?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>
<!--配置properties文件-->
<properties resource="jdbc.properties"/>
<!--settings:控制mybatis全局行为-->
<settings>
<!--设置mybatis输出日志-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--添加PageHelper的配置-->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor" />
</plugins>
<environments default="test">
<environment id="test">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!--数据库的驱动类名-->
<property name="driver" value="${jdbc.driver}"/>
<!--连接数据库的url字符串-->
<property name="url" value="${jdbc.url}"/>
<!--访问数据库的用户名-->
<property name="username" value="${jdbc.username}"/>
<!--密码-->
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
<!-- sql mapper(sql映射文件)的位置-->
<mappers>
<!-- <mapper resource="com/yang/dao/StudentDao.xml"/>-->
<!--用package代替mapper,表示dao包下的所有mapper.xml文件-->
<package name="com.yang.dao"/>
</mappers>
</configuration>
数据库配置文件:
jdbc.properties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm_learning?serverTimezone=UTC
jdbc.username=root
jdbc.password=020216
查询测试
完成基本的配置后,实现分页查询只需要在需要进行分页的 MyBatis 查询方法前调用 PageHelper.startPage() 静态方法即可,紧跟在这个方法后的第一个 MyBatis 查询方法会被进行分页;
测试代码:
@Test
public void selectTest01() {
SqlSession session = MybatisUtil.getSqlSession();
StudentDao studentDao = session.getMapper(StudentDao.class);
// pageNum:第几页(从1开始)
// pageSize:页几行数据
// 表示第一页,一页三行数据
PageHelper.startPage(1, 3); // 在执行下面的查询方法selectStudent()之前调用
List<Student> list = studentDao.selectStudent();
list.forEach(stu-> System.out.println(stu));
}
输出结果:
这就输出了第一页的三行内容,如果想看第二页的内容,稍改代码:
PageHelper.startPage(2, 3); // 第二页的三行数据
非常方便简单;并且PageHelper支持多种数据库,绝对够用的;
八、总结
第一次接触框架,配置挺多的,也不知道理解有没有偏差,或者语言描述有问题,如果内容哪里有问题也欢迎指正!
这个笔记只是记录了一些比较重要的部分,Mybatis并不是仅仅这些内容,后期如果遇到新的内容会再补充上去;
欢迎大家的点评!