1.什么是Mybatis
MyBatis 是一款优秀的持久层框架(也可以说mybatis是一款优秀的ORM框架),它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
ORM是Object Relational Mapping的简称,中文叫对象关系映射,对象关系映射通过使用描述对象与数据库之间映射的元数据,将面向对象语言程序的对象自动持久化到关系型数据库中。
2.Mybatis的历史
MyBatis的前身是iBATIS,是Clinton Begin在2001年发起的一个开源项目,最初侧重于密码软件的开发,后来发展成为一款基于 Java的持久层框架。2004年,Clinton将iBATIS的名字和源码捐赠给了Apache软件基金会,接下来的6年中,开源软件世界发生了巨大的变化,一切开发实践、基础设施、许可,甚至数据库技术都彻底改变了。2010年,核心开发团队决定离开Apache软件基金会,并且将iBATIS改名为MyBatis。
3.为什么使用Mybatis?
java持久层框架操作关系型数据的本质就是使用jdbc。JDBC是SUN公司提供的JAVA语言访问关系型数据的技术规范。JDBC的缺点如下:
(1).需要频繁的创建数据库连接。
(2).数据库简单归纳为CRUD(增删改查)4个操作,使用JDBC实现CRUD我们需要编写大量代码。
(3).对底层事务、数据类型转换支持差,这些都需要开发者手动去解决。
本质上ORM框架就是对JDBC的进一步封装,mybatis封装了JDBC对关系型数据库的各种操作,简化了开发;增加了连接池和一、二缓存,大幅度减少了资源的开销;支持动态SQL。
4.mybatis与hibanate的区别?
java常用的持久层有Mybatis、Hibernate、Spring Data JPA等等。Mybatis由于属于半自动框架,需要开发人员手写SQL语句,开发人员可以对SQL进行调优从而提升系统性能.这无疑在要求快速响应场景下是一个巨大的优势。
从开发方面来讲:
- Hibernate是全自动ORM框架,提供了基本CURD封装,所以开发速度很快,但可以使用MybatisPlus提升开发效率,MybatisPlus是基于Mybatis封装的工具库,提供了CURD封装和开发中常用功能,使开发者更专注与业务,提升开发效率。
- Mybatis属于半自动ORM框架,需要开发人员手写SQL语句,开发效率相较于比Hibernate低。
从sql优化来看:
- Hibernate会自动生成HSQL语句,内部显示比较复杂,不利于SQL语句优化。
- Mybatis是手写SQL,可以通过SQL语句调优来提升性能。
从对象管理来看:
- Hibernate属于完整的对象关系映射框架,无需关注底层实现,即可管理对象。
- Mybatis需要自行管理。
从缓存来看:
- Hibernate和Mybatis的二级缓存除了采用系统默认的缓存机制外,都可以通过实现你自己的缓存或为其他第三方缓存方案,创建适配器来完全覆盖缓存行为。
- Hibernate的二级缓存配置在SessionFactory生成的配置文件中进行详细配置,然后在具体的表-对象映射中配置缓存。MyBatis的二级缓存配置都是在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制。并且Mybatis可以在命名空间中共享相同的缓存配置和实例,通过Cache-ref来实现。
5.Quick start(快速开始,通过从 XML 中构建 SqlSessionFactory)
Mybatis提供了两种方式获取SqlSessionFactory,第一种是通过读取XML文件构建SqlSessionFactory,第二种是通过编写Java代码配置构建SqlSessionFactory。5.1 创建数据库和相关表,sql语句如下:
```sql
drop database if exists test; create database test; use test;
drop table if exists student;
create table student( id int primary key not null auto_increment, stuName varchar(25) not null comment ‘姓名’, stuNo varchar(25) not null comment ‘学号’, age tinyint not null comment ‘age’, sex tinyint not null comment ‘性别,0男1女’, address varchar(200) comment ‘地址’, birthday TIMESTAMP comment ‘生日’, unique index(stuNo) using btree )engine=INNODB character set=utf8mb4 collate=utf8mb4_general_ci row_format=dynamic comment ‘学生表’;
insert into student(stuName,stuNo,age,sex,address,birthday) values(‘大白’,’s1000’,20,0,’南城’,’2020-01-01’), (‘小黄’,’s1001’,10,0,’东城’,’2010-01-01’), (‘老王’,’s1002’,66,0,’北城’,’1987-02-01’), (‘zxp’,’s1003’,24,0,’通天河’,’2012-01-01’), (‘黄武’,’s1004’,40,0,’天庭’,’2013-01-01’), (‘王麻子’,’s1005’,96,0,’贝菊门’,’1997-02-01’);
<a name="k2zji"></a>
#### 5.2 创建maven项目,添加mybatis依赖及其相关依赖
添加日志相关包是为了打印Mybatis生成的SQL语句。junit用于单元测试,lombok用于简化生成getters setters 构造函数(idea 要安装Lombok插件)。
```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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.fly</groupId>
<artifactId>mybatis-example01</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
</properties>
<dependencies>
<!-- mybatis依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.6</version>
</dependency>
<!-- mysql8依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.22</version>
</dependency>
<!--
slf4j-api、slf4j-log4j12、log4j都是日志相关的依赖。
slf4j-api只提供了日志的接口,并没有提供实现
log4j实现了日志接口但不能通过日志接口直接访问
slf4j-log4j12用于转换各日志实现包的使用
-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.0-alpha1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>2.0.0-alpha1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
</dependency>
<!-- lombok 简化class getter setter -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- maven编译插件,下面指定了jdk版本编译 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
5.3 创建Mybatis配置文件
在src/main/resources下创建Mybatis配置文件,名为mybatis.xml,由于ide对Mybatis的配置文件支持并不是很友好,所以需要我们自定义Mybatis模板。
模板内容为:
<?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>
</configuration>
在src/resources下创建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>
<!-- environments用于指定运行环境配置,default="development"指定运行环境为开发环境 -->
<environments default="development">
<environment id="development">
<!-- 配置事务管理器 类型为JDBC -->
<transactionManager type="JDBC"/>
<!-- 配置数据源 数据源类型为POOLED -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&allowMultiQueries=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<!-- mappers指定一组mapper(映射器) -->
<mappers>
<!-- mapper的XML映射文件包含了SQL代码和映射定义信息,resource指定mapper文件的地址。
待会要在src/main/resources目录下创建一个mapper文件夹,
然后在mapper文件夹创建一个名为studentMapper.xml的mapper映射文件
-->
<mapper resource="mapper/studentMapper.xml"/>
</mappers>
</configuration>
5.4 创建Java实体类和对应DAO层
ORM中的对象就是Java中的实体类,DAO用于数据持久层操作,Mapper XML 通过namespace(命名空间)关联一个DAO,DAO中的方法名称对应Mapper XML的select、insert、delete、update标签,而这些标签里面就是我们常写的SQL语句。
com.fly.entity.Student.class:
package com.fly.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
private Integer id;
private String stuName;
private String stuNo;
private Integer age;
private Integer sex;
private String address;
private String birthday;
}
com.fly.mapper.StudentMapper.class:
package com.fly.mapper;
import com.fly.entity.Student;
import org.apache.ibatis.annotations.Param;
import java.util.List;
public interface StudentMapper {
int addStudent(@Param("student") Student student);
int delStudentById(@Param("id") int id);
int upStudent(@Param("student")Student student);
Student queryStudentById(@Param("id") int id);
List<Student> queryStudent();
}
5.5 创建Mapper XML并设置映射信息
Mapper XML中的映射可分为两种,第一种是Java对象与数据库字段的关系映射,第二种是DAO层类中的方法与Mapper XML的select、insert、delete、update标签的映射。
在src/main/resources下创建mapper文件夹,然后在mapper文件夹创建studentMapper.xml。mapper XML的命令方式一般是以Mapper结尾。
<?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与javaDao的映射关系 唯一 -->
<mapper namespace="com.fly.mapper.StudentMapper">
<!-- 设置java实体对象与数据库字段的映射关系 -->
<resultMap id="studentMap" type="com.fly.entity.Student">
<id property="id" column="id"/>
<result property="stuName" column="stuName"/>
<result property="stuNo" column="stuNo"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="address" column="address"/>
<result property="birthday" column="birthday"/>
</resultMap>
<!-- 定义sql片段 如果多个语句有重新的内容,可以把它提取到一个sql标签,然后通过
<include/>标签的refid属性指定引入sql标签的id即可 -->
<sql id="student_sql">
id,stuName,stuNo,age,sex,address,birthday
</sql>
<!--
insert标签标识是一个插入操作,id="addStudent"表示当前操作与
com.fly.mapper.StudentMapper的addStudent是对应关系,
parameterType用于指定addStudent方法中参数的类型。
sqlSession.insert()传入对象参数并不需要#{参数名.属性名}这种形式访问属性,它会对对象进行处理,使得我们可以直接访问属性,
例如传入的参数是student,如果要访问stuName属性,写成#{stuName}即可访问
-->
<insert id="addStudent" parameterType="com.fly.entity.Student">
insert into student(stuName,stuNo,age,sex,address,birthday) values(#{stuName},
#{stuNo},#{age},#{sex},#{address},#{birthday});
</insert>
<!--由于delStudentById方法传入一个int 类型的参数,所以parameterType的值为java.lang.Integer -->
<delete id="delStudentById" parameterType="java.lang.Integer">
delete from student where id=#{id}
</delete>
<update id="upStudent" parameterType="com.fly.entity.Student">
update student set stuName=#{stuName} where id=#{id}
</update>
<!--
parameterType用于设置对应DAO类中方法的参数类型
resultType用于设置对饮DAO类中方法的返回值类型,insert、delete、update标签都不用写resultType属性,
默认返回int类型
-->
<select id="queryStudentById" parameterType="java.lang.Integer" resultType="com.fly.entity.Student">
select <include refid="student_sql"/> from student where id=#{id};
</select>
<select id="queryStudent" resultMap="studentMap">
select <include refid="student_sql"/> from student;
</select>
</mapper>
配置log4j日志查看mybatis 生成SQL语句,在src/main/resources目录下创建log4j.properties,内容如下:
log4j.rootLogger=ERROR,stdout
#mybatis 日志配置
log4j.logger.com.fly.mapper=TRACE
#控制台输出配置
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n
5.6 测试
Mybatis操作数据的流程如下:
(1).读取mybatis配置,通过new SqlSessionFactoryBuilder().build(stream)创建一个SqlSessionFactory。
(2).通过SqlSessionFactory的openSession()打开Session获取一个SqlSession对象。
(3).通过SqlSession提供的API操作数据,例如SqlSession.insert(“addStudent”),这里的addStudent是指定执行在studentMapper.xml中insert标签id为addStudent的SQL语句。
(4).对于insert、delete、update操作需要使用SqlSession.commit()提交事务,数据库才会完成对应操作,否则即使操作成功了但数据库中的数据仍没有变化。
(5).关闭SqlSession连接。
package com.fly;
import com.fly.entity.Student;
import com.fly.mapper.StudentMapper;
import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
import org.junit.BeforeClass;
import org.junit.Test;
import javax.sql.DataSource;
import java.io.InputStream;
import java.util.List;
/**
* 基于xml形式构建SqlSessionFactory
*/
public class Test01 {
private static SqlSessionFactory sqlSessionFactory;
private static SqlSession sqlSession;
/*通过xml构建sqlSessionFactory*/
//@BeforeClass表示针对所有测试,只执行一次,且必须为static void
@BeforeClass
public static void init(){
try{
InputStream stream= Resources.getResourceAsStream("mybatis.xml");
//SqlSessionFactoryBuilder通过获取配置文件信息得到SqlSessionFactory,看到build()就想到了建造者模式
sqlSessionFactory=new SqlSessionFactoryBuilder().build(stream);
sqlSession=sqlSessionFactory.openSession();
stream.close();
}catch (Exception e){
e.printStackTrace();
}
}
/*通过编写java配置构建sqlSessionFactory*/
@Test
public void initConfig(){
DataSource dataSource=new PooledDataSource("com.mysql.cj.jdbc.Driver",
"jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&allowMultiQueries=true",
"root","root");
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.setMapUnderscoreToCamelCase(true);
//设置Mapper
configuration.addMapper(StudentMapper.class);
sqlSessionFactory=new SqlSessionFactoryBuilder().build(configuration);
sqlSession=sqlSessionFactory.openSession();
/*
* 如果出现Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not
* contain value for com.fly.mapper.StudentMapper.queryStudentById 异常是idea的原因,idea默认不是编译java文件夹下的.xml文件,
* 如果想测试的话,直接把studentMapper.xml复制到target/classes/com/fly/mapper目录下即可
* */
System.out.println(sqlSession.selectList("com.fly.mapper.StudentMapper.queryStudentById",4));
}
@Test
public void addStudent(){
Student student=new Student();
student.setStuName("张三");
student.setStuNo("s10001");
student.setAge(20);
student.setSex(0);
student.setAddress("天空城");
student.setBirthday("2020-01-01");
//表示调用Mapper XML文件中insert标签的id为addStudent的语句并传入student参数执行
int row=sqlSession.insert("addStudent",student);
//提交成功数据库才有记录
sqlSession.commit();
System.out.println(row>0?"添加成功!":"添加失败!");
Test01.close();
}
@Test
public void delStudentById(){
int row = sqlSession.delete("delStudentById", 9);
//提交成功数据库才有记录
sqlSession.commit();
System.out.println(row>0?"删除成功!":"删除失败!");
Test01.close();
}
@Test
public void upStudent(){
Student student=new Student();
student.setId(1).setStuName("mybatis");
int row = sqlSession.update("upStudent", student);
//提交成功数据库才有记录
sqlSession.commit();
System.out.println(row>0?"修改成功!":"修改失败!");
Test01.close();
}
@Test
public void queryStudentById(){
Student student = (Student)sqlSession.selectOne("com.fly.mapper.StudentMapper.queryStudentById", 4);
System.out.println(student);
Test01.close();
}
@Test
public void queryStudent(){
//selectList中填的是mapper中的方法
List<Object> selectList = sqlSession.selectList("com.fly.mapper.StudentMapper.queryStudent");
Test01.close();
}
public static void close(){
try{
}catch (Exception e){
e.printStackTrace();
}finally {
sqlSession.close();
}
}
}
6.通过Java配置构建SqlSessionFactory
改造Test01.class的init方法
/*通过编写java配置构建sqlSessionFactory*/
@Test
public void init(){
DataSource dataSource=new PooledDataSource("com.mysql.cj.jdbc.Driver",
"jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC&allowMultiQueries=true",
"root","root");
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
//设置Mapper
configuration.addMapper(StudentMapper.class);
sqlSessionFactory=new SqlSessionFactoryBuilder().build(configuration);
sqlSession=sqlSessionFactory.openSession();
//这里访问的是DAO对应的方法
System.out.println(sqlSession.selectList("com.fly.mapper.StudentMapper.queryStudentById",4));
}
此时会报一个错误:
Error querying database. Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for com.fly.mapper.StudentMapper.queryStudentById
简单来说找不到Mapper XML信息,上面代码看起来确实没有配置Mapper XML,configuration.addMapper(StudentMapper.class) 这行代码表示添加Mapper信息,等同于mybatis XML配置中的mapper标签,addMapper传入的是一个类路径(StudentMapper.class的类路径),如果此类路径下有一个同名XML,那么会自动加载这个XML。
上面问题产生的原因是idea编译的问题,他默认不是编译java文件夹下的.xml文件,这里我们可以在配置文件中添加这块的编译,在pom.xml文件中build标签里添加如下内容(但还是没有解决):
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>classpath</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
解决方法2:直接将Mapper XML 拖到target 对应的目录,再次运行成功。