1. MyBatis 持久层框架
1.1概念
MyBatis的前身就是iBatis,iBatis本是apache的一个开源项目,2010年5月这个项目由apahce sofeware foundation 迁移到了google code,并且改名为MyBatis。
MyBatis 是支持普通 SQL 查询,存储过程和高级映射的优秀持久层框架。MyBatis 消除了几乎所有的 JDBC 代码和参数的手工设置以及结果集的检索。
优点:
(1)简化JDBC的开发。
(2)能够更好的完成ORM(对象关系映射),是指类和表的关系,是属性和字段值的关系。
1.2 内部组件结构图
1.3 核心组件
- 核心配置文件:mybatis-config.xml,里面写数据源,事务,映射文件
- 映射文件:XxxMapper.xml ,里面写了大量的SQL语句
- SqlSessionFactory:是SQL的会话工厂,用来产生很多的会话,对象唯一共享
- SqlSession:是SQL的会话,用来执行SQL
- ORM:对象关系的自动映射,把car表里的字段的值查出来,一个一个,给Car类里的属性赋值
2. 入门案例1 --Mybatis的XML文件映射方式
2.1 准备数据库表
create database mybatisdb default character set utf8;
use mybatisdb;
create table user(id int primary key auto_increment,name varchar(100),addr varchar(100),age int);
Insert into user values(null,'hanmeimei','北京',28);
Insert into user values(null,'xiongda','上海',20);
Insert into user values(null,'xiaonger','上海',19);
DROP TABLE IF EXISTS `dept`;
CREATE TABLE `dept` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`dname` varchar(14) DEFAULT NULL,
`loc` varchar(13) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of dept
-- ----------------------------
INSERT INTO `dept` VALUES ('1', '呵呵呵', '一区');
INSERT INTO `dept` VALUES ('2', '哈哈哈哈', '二区');
INSERT INTO `dept` VALUES ('3', 'operations', '二区');
INSERT INTO `dept` VALUES ('5', 'java教研部', '大钟寺');
INSERT INTO `dept` VALUES ('10', '开发', '西二旗');
DROP TABLE IF EXISTS `emp`;
CREATE TABLE `emp` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ename` varchar(10) DEFAULT NULL,
`job` varchar(9) DEFAULT NULL,
`mgr` decimal(4,0) DEFAULT NULL,
`hiredate` date DEFAULT NULL,
`sal` decimal(7,2) DEFAULT NULL,
`comm` decimal(7,2) DEFAULT NULL,
`deptno` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=510 DEFAULT CHARSET=utf8;
-- ----------------------------
-- Records of emp
-- ----------------------------
INSERT INTO `emp` VALUES ('100', 'jack', '副总', null, '2002-05-03', '90000.00', null, '1');
INSERT INTO `emp` VALUES ('200', 'tony', '总监', '100', '2015-02-02', '10000.00', '2000.00', '2');
INSERT INTO `emp` VALUES ('300', 'hana', '经理', '200', '2017-02-02', '8000.00', '1000.00', '2');
INSERT INTO `emp` VALUES ('400', 'leo', '员工', '300', '2019-02-22', '3000.00', '200.12', '2');
INSERT INTO `emp` VALUES ('500', 'liu', '员工', '300', '2019-03-19', '3500.00', '200.58', '2');
INSERT INTO `emp` VALUES ('502', '王一博', 'topidol.', '1000', '2021-03-31', '20000.00', '99.00', '88');
INSERT INTO `emp` VALUES ('504', '蔡徐坤', 'rapper', '10', '2021-03-29', '100.00', '1000.00', '100');
2.2 创建Maven工程
2.3 配置Pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.tedu</groupId>
<artifactId>springmybatis-demo2</artifactId>
<version>1.0-SNAPSHOT</version>
<name>springmybatis-demo2</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--mybatis依赖包-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!--jdbc依赖包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.3</version>
</plugin>
</plugins>
</build>
</project>
2.4 创建User类
package cn.tedu.pojo;
/**
* 封装User表里的每个字段值,要求类名与表明对应,属性名与字段名一致
*/
public class User {
private Integer id;
private String name;
private String addr;
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 getAddr() {
return addr;
}
public void setAddr(String addr) {
this.addr = addr;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", addr='" + addr + '\'' +
", age=" + age +
'}';
}
}
2.5 创建核心配置文件mybatis-config.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">
<!-- mybatis框架的核心配置文件 ,配置了数据源、事务、映射文件-->
<configuration>
<environments default="test">
<!--配置数据源、事务-->
<environment id="test">
<!--配置了事务,使用jdbc提供事务-->
<transactionManager type="JDBC"></transactionManager>
<!--配置了数据源,指定了连接数据库的4个参数-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatisdb?characterEncoding=utf8&serverTimezone=Asia/Shanghai" />
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--用来引入映射文件-->
<mappers>
<mapper resource="UserMapper.xml"></mapper>
</mappers>
</configuration>
2.6 配置映射文件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">
<!--mybatis的映射文件,用来写大量的SQL;namespace是命名空间,用来作为每个映射文件的唯一标识。-->
<mapper namespace="cn.tedu.dao">
<select id="getUser" resultType="cn.tedu.pojo.User">
select * from user
</select>
</mapper>
2.7 测试
package cn.tedu;
import cn.tedu.pojo.User;
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.jupiter.api.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
//测试mybatis
public class TestMybatis {
@Test
public void getAll() throws IOException {
//1.读取核心配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
//2.创建会话工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3.打开SQL会话
SqlSession session = factory.openSession();
//4.执行SQL -- 定位SQL的方式是:namespace的值.id的值
List<User> list = session.selectList("cn.tedu.dao.getUser");
//5.遍历list
for (User u : list){
System.out.println(u);
}
}
}
运行结果:
Mybatis为了提高查询效率,提供了缓存技术(基于SqlSession会话来实现,必须为同一个session)。当第一次查询数据时,缓存中没有,会直接去查询数据库,把数据存入缓存中。第二次查询相同数据时,先去查询缓存,如果有就会直接获取,没有再去查询数据库。
3 入门案例2 --Mybatis的接口映射方式
3.1 概述
在上面的入门案例中,在调用session方法时,都会传入要调用的SQL的namespace+id名称。这种方式很容易出错,因为是String类型的,所以,mybatis提供了一种非常好的设计方式来避免这种问题,即:Mapper接口映射方式。
3.2 创建Maven工程
3.3 配置pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.tedu</groupId>
<artifactId>springmybatis-demo3</artifactId>
<version>1.0-SNAPSHOT</version>
<name>springmybatis-demo3</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--mybatis依赖包-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!--jdbc依赖包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.48</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.3</version>
</plugin>
</plugins>
</build>
</project>
3.4 创建Dept类
package cn.tedu.pojo;
//完成ORM,自动实现把dept表里字段的值 封装给不同属性
public class Dept {
private Integer id;//描述dept表里的id字段
private String dname;//描述dept表里的dname字段
private String loc;//描述dept表里的loc字段
//set get toString
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getDname() {
return dname;
}
public void setDname(String dname) {
this.dname = dname;
}
public String getLoc() {
return loc;
}
public void setLoc(String loc) {
this.loc = loc;
}
@Override
public String toString() {
return "Dept{" +
"id=" + id +
", dname='" + dname + '\'' +
", loc='" + loc + '\'' +
'}';
}
}
3.5 创建DeptMapper.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属性的值是接口的全路径
-->
<mapper namespace="cn.tedu.dao.DeptDao">
<!--select用来标记着这是一个查询的SQL语句
id属性是SQL的唯一标识
id属性的值是 接口中对应的方法名
resultType属性的值是 要把查询结果封装给哪个类的全路径
-->
<select id="getAll" resultType="cn.tedu.pojo.Dept">
select * from dept
</select>
</mapper>
3.6 创建核心配置文件
<?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">
<!-- mybatis框架的核心配置文件 ,配置了数据源、事务、映射文件-->
<configuration>
<environments default="test">
<!--配置数据源、事务-->
<environment id="test">
<!--配置了事务,使用jdbc提供事务-->
<transactionManager type="JDBC"></transactionManager>
<!--配置了数据源,指定了连接数据库的4个参数-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatisdb?characterEncoding=utf8&serverTimezone=Asia/Shanghai" />
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!--用来引入映射文件-->
<mappers>
<mapper resource="DeptMapper.xml"></mapper>
</mappers>
</configuration>
3.7 测试代码
package cn.tedu;
import cn.tedu.dao.DeptDao;
import cn.tedu.pojo.Dept;
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.jupiter.api.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
//测试Mybatis接口映射的方式
public class TestMabatis {
@Test
public void getDept() throws IOException {
//1.读取核心配置文件
InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
//2.常见会话工厂
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
//3.开启会话
SqlSession session = factory.openSession();
//4.执行SQL ,执行接口中的抽象方法 --- 参数是接口文件的class对象
DeptDao dao = session.getMapper(DeptDao.class);
List<Dept> list = dao.getAll();
for (Dept d : list){
System.out.println(d);
}
}
}
运行结果:
4. 解析SQL参数
4.1 获取用户传入的参数
获取用户传入的参数,固定语法:#{传入的参数名}或者${传入的参数名}
<select id="getDept" resultType="Dept">
select * from dept where id = #{id}
</select>
说明:$与#传值的区别
- 使用#做参数传值时,使用新对象传递,防止SQL注入,如果字段参数是字符串类型的,会自动拼接双引号,高效安全。
- 使用$做参数传值时,直接把参数拼在SQL中,存在SQL注入问题,不会为字符串类型的参数拼接双引号,执行时就会产生SQL错误。
4.2 参数类型 paramterType
指定参数类型,通常指定一个对象类型。
4.3 返回值类型
resultType
一般用于查询过程,把结果集转换成指定的对象实例。
resultMap
如果类中的属性名与表中的字段名一致时,可以自动完成ORM映射关系。
如果类中的属性名与表中的字段名不一致时,resultType无法完成ORM映射,需要使用resultMap。
- 用法:
把SQL标签的属性resultType
改为resultMap
,并单独处理属性名与字段名不一致的特殊情况。 - 问题:
- 解决方案
<!--当字段名与属性名不一致时,可用resultMap;id属性:每个resultMap的唯一标识,type属性:需要ORM映射的类的全路径-->
<resultMap id="UserERM" type="cn.tedu.pojo.UserExtra">
<!--column描述表中的字段名,propery描述的类中的属性名-->
<result column="user_id" property="userId"></result>
</resultMap>
<select id="getUser" resultMap="UserERM">
select * from user_extra
</select>
- 可得正确结果:
起别名
<typeAliases>
<typeAlias type=" " alias=" "></typeAlias>
</typeAliases>
第一步:在核心配置文件中添加代码。
第二步:修改映射文件。
5. 动态SQL
Mybatis提供使用ognl表达式动态生成sql的功能。
5.1 sql和include 标签
- sql标签用来提取SQL片段,来提高SQL的复用。
- include标签用来引用指定的SQL片段。
<!--动态Sql标签,用来提取共性sql-->
<sql id="colums">
id,dname,loc
</sql>
<select id="getAll" resultType="Dept">
select
<!--引用指定片段-->
<include refid="colums"></include>
from dept
</select>
5.2 if 标签
执行SQL时,可以添加一些判断条件。
if标签用来做判断,满足条件才执行SQL,test属性用来写判断条件
<select id="getDept" resultType="Dept">
select *
from dept where
<if test="id!=null">
id = #{id}
</if>
</select>
5.3 where 标签
去掉条件中可能多余的and或者or;
<select id="find" resultType="Item" parameterType="Item">
SELECT <include refid="cols"/> FROM tb_item
<where>
<if test="title != null"> title like #{title} </if>
<if test="sellPoint != null">and sell_point like #{sellPoint}</if>
</where>
</select>
5.4 set 标签
去掉最后可能多余的逗号:
<update id="update">
UPDATE teachers
<set>
<if test="tname != null">tname=#{tname},</if>
<if test="tsex != null">tsex=#{tsex},</if>
<if test="tbirthday != null">tbirthday=#{tbirthday},</if>
<if test="prof != null">prof=#{prof},</if>
<if test="depart != null">depart=#{depart}</if>
</set>
WHERE tno=#{tno}
</update>
5.5 foreach 标签
用于in子查询中的多个值的遍历;
List<Dept> getById(Integer[] ids);
List<Dept> ary = dao.getById(new Integer[]{1, 2, 3});
<select id="getById" resultType="Dept">
select *
from dept
where id in
<!--获取数组中的数据,collection的值是固定值:array-->
<foreach collection="array" open="(" close=")" separator="," item="i">
#{i}
</foreach>
</select>
6. 小结分析
6.1 JDBC和MyBatis的区别
-
JDBC是java提供了一套专门用于和数据库对接的api,java.sql.*,其规范了如何和数据库进行对接,实现由各数据库厂商进行各自的实现和扩展。学习JDBC重点在学习如何使用其api。
-
MyBatis框架是轻量级封装了JDBC,我们已经看不到这些api,连接connection、语句preparedstatement、结果集ResultSet,而关注的是mybatis框架体系如何去使用,一旦写好,我们关注的是java对象。
6.2 XML和接口方式的区别?
- MyBatis提供了两种操作数据库的方式,一种是通过xml映射文件,一种是通过java的接口类。按面向对象方式更加推荐接口方式,但如果复杂的多表映射,仍然需要使用xml映射文件的ResultMap方式实现。
- 接口只是假象,其底层仍然是通过xml实现,好不容易实现了一套方式,怎忍丢掉呢?可以做个测试就知道它底层怎么实现的?把xml中的sql删除,它就玩不转了。
6.3 接口方式怎么找到xml执行的?
SqlSession的getMapper方法找到类,通过反射可以获取到类的全路径(包名.类名),相加后就定位到某个xml的命名空间namespace,在根据调用的方法去找到xml中某个标签的id属性。从而实现价值接口,调用接口的方法而间接找到xml中的标签,通过解析xml获取这个标签的内容,从而获取到sql语句。