MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。
简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。
灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。
提供映射标签,支持对象与数据库的orm字段关系映射
提供对象关系映射标签,支持对象关系组建维护
提供xml标签,支持编写动态sql。
触发条件:加载配置文件
处理过程:将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中。
触发条件:调用Mybatis提供的API
传入参数:为SQL的ID和传入参数对象
处理过程:将请求传递给下层的请求处理层进行处理。
触发条件:API接口层传递请求过来
传入参数:为SQL的ID和传入参数对象
处理过程:
(A)根据SQL的ID查找对应的MappedStatement对象。
(B)根据传入参数对象解析MappedStatement对象,得到最终要执行的SQL和执行传入参数。
(C)获取数据库连接,根据得到的最终SQL语句和执行传入参数到数据库执行,并得到执行结果。
(D)根据MappedStatement对象中的结果映射配置对得到的执行结果进行转换处理,并得到最终的处理结果。
(E)释放连接资源。
(1)API接口层:提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
(2)数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
(3)基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。
框架架构讲解:
(1)加载配置:配置来源于两个地方,一处是配置文件,一处是Java代码的注解,将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中。
(2)SQL解析:当API接口层接收到调用请求时,会接收到传入SQL的ID和传入对象(可以是Map、JavaBean或者基本数据类型),Mybatis会根据SQL的ID找到对应的MappedStatement,然后根据传入参数对象对MappedStatement进行解析,解析后可以得到最终要执行的SQL语句和参数。
(3)SQL执行:将最终得到的SQL和参数拿到数据库进行执行,得到操作数据库的结果。
(4)结果映射:将操作数据库的结果按照映射的配置进行转换,可以转换成HashMap、JavaBean或者基本数据类型,并将最终结果返回。
MyBatis 最强大的特性之一就是它的动态语句功能。如果您以前有使用JDBC或者类似框架的经历,您就会明白把SQL语句条件连接在一起是多么的痛苦,要确保不能忘记空格或者不要在columns列后面省略一个逗号等。动态语句能够完全解决掉这些痛苦。
尽管与动态SQL一起工作不是在开一个party,但是MyBatis确实能通过在任何映射SQL语句中使用强大的动态SQL来改进这些状况。动态SQL元素对于任何使用过JSTL或者类似于XML之类的文本处理器的人来说,都是非常熟悉的。在上一版本中,需要了解和学习非常多的元素,但在MyBatis 3 中有了许多的改进,现在只剩下差不多二分之一的元素。MyBatis使用了基于强大的OGNL表达式来消除了大部分元素。
JDBC 类型 | Java 类型 |
CHAR | String |
VARCHAR | String |
LONGVARCHAR | String |
NUMERIC | java.math.BigDecimal |
DECIMAL | java.math.BigDecimal |
BIT | boolean |
BOOLEAN | boolean |
TINYINT | byte |
SMALLINT | short |
INTEGER | int |
BIGINT | long |
REAL | float |
FLOAT | double |
DOUBLE | double |
ARRAY | Array |
BINARY | byte[] |
VARBINARY | byte[] |
LONGVARBINARY | byte[] |
DATE | java.sql.Date |
TIME | java.sql.Time |
TIMESTAMP | java.sql.Timestamp |
CLOB | Clob |
BLOB | Blob |
DISTINCT | mapping of underlying type |
STRUCT | Struct |
REF | ref |
DATALINK | java.net.URL[color=red][/color] |
use test;
create table user(
id TINYINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(20) NOT NULL,
age INT(3) NOT NULL DEFAULT 0
)ENGINE=InnoDB DEFAULT CHARSET=utf8;
select * from user;
INSERT INTO user1 VALUES(NULL,'张三',18),(NULL,'张四',20),(NULL,'张五',25);
org.mybatis
mybatis
3.5.6
mysql
mysql-connector-java
8.0.15
org.projectlombok
lombok
1.16.18
junit
junit
4.12
src/main/java
**/*.properties
**/*.xml
true
src/main/resources
**/*.properties
**/*.xml
true
package com.xx.entity;
public class User {
private int id;
private String name;
private int age;
public User() {
}
public User(int id, String name, int age) {
this.id = id;
this.name = name;
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 int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
package com.xx.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 sqlSessionFactory;
static {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
return sqlSessionFactory.openSession();
}
}
package com.xx.mapper;
import com.xx.entity.User;
import java.util.List;
import java.util.Map;
public interface UserMapper {
public List selectUsers();
public User getUserById(int id);
public List selectUserLikeName(String name);
public int addUser(User user);
public int addUserByMap(Map map);
public int updateUserById(User user);
public int updateUserByMap(Map map);
public int deleteUserById(int id);
}
insert into test.user values (#{id},#{name},#{age})
insert into test.user values (#{userId},#{userName},#{userAge})
update test.user set age = #{age},name=#{name} where id = #{id}
update test.user
name = #{userName,jdbcType=VARCHAR},
age = #{userAge,jdbcType=INTEGER}
where id = #{userId,jdbcType=TINYINT};
delete from test.user where id=#{id}
package com.xx.mapper;
import com.xx.entity.User;
import com.xx.util.MybatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class UserMapperTest {
public SqlSession sqlSession= MybatisUtil.getSqlSession();;
@Test
public void testUserMapper(){
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List users = mapper.selectUsers();
for (User user : users) {
System.out.println(user);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
}
}
@Test
public void testSelectUserById(){
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.getUserById(2);
System.out.println(user);
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
}
}
@Test
public void testSelectUserLikeName(){
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List users = mapper.selectUserLikeName("葛");
for (User user : users) {
System.out.println(user);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
sqlSession.close();
}
}
/**
* 增删改需要提交事务
*/
@Test
public void testAddUser(){
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.addUser(new User(4, "asd", 20));
if (i>0){
sqlSession.commit();
}
System.out.println(i);
} catch (Exception e) {
e.printStackTrace();
sqlSession.rollback();
} finally {
sqlSession.close();
}
}
@Test
public void testAddUserByMap(){
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map map=new HashMap();
map.put("userId",7);
map.put("userName","诸葛");
map.put("userAge",18);
int i = mapper.addUserByMap(map);
if (i>0){
sqlSession.commit();
}
System.out.println(i);
} catch (Exception e) {
e.printStackTrace();
sqlSession.rollback();
} finally {
sqlSession.close();
}
}
@Test
public void testUpdateUserById(){
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.updateUserById(new User(4,"马六",30));
if (i>0){
sqlSession.commit();
}
System.out.println(i);
} catch (Exception e) {
e.printStackTrace();
sqlSession.rollback();
} finally {
sqlSession.close();
}
}
@Test
public void testUpdateUserByMap(){
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
Map map=new HashMap();
map.put("userId",6);
map.put("userName","诸葛孔明");
map.put("userAge",20);
int i = mapper.updateUserByMap(map);
if (i>0){
sqlSession.commit();
}
System.out.println(i);
} catch (Exception e) {
e.printStackTrace();
sqlSession.rollback();
} finally {
sqlSession.close();
}
}
@Test
public void testDeleteUserById(){
try {
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
int i = mapper.deleteUserById(4);
if (i>0){
sqlSession.commit();
}
System.out.println(i);
} catch (Exception e) {
e.printStackTrace();
sqlSession.rollback();
} finally {
sqlSession.close();
}
}
}
排查思路: mapper-config配置文件中未配置mapper.xml
排查思路:
1.查看mapper.xml 中 mapper属性 namespace 是否和 文件所在包名一致
2.在mapper-config配置文件中使用
3.在mapper-config配置文件中使用
4.在mapper-config配置文件中使用
public int addUser(User user);
public int addUserByMap(Map map);
insert into test.user values (#{id},#{name},#{age})
insert into test.user values (#{userId},#{userName},#{userAge})
public int updateUserById(User user);
public int updateUserByMap(Map map);
update test.user set age = #{age},name=#{name} where id = #{id}
update test.user
name = #{userName,jdbcType=VARCHAR},
age = #{userAge,jdbcType=INTEGER},
where id = #{userId,jdbcType=TINYINT};
public int deleteUserById(int id);
delete from test.user where id=#{id}
public User getUserById(int id);
public List selectUserLikeName(String name);
configuration(配置)
MyBatis 可以配置成适应多种环境,例如,开发、测试和生产环境。
但: 每个SqlSessionFactory实例只能选择一个环境。
Mybatis默认事务管理器是 JDBC、 连接池。
db.properties
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&serverTimezone=UTC
username=username
password=password
mybatis-config.xml中引入 db.properties
属性配置位置要求:
类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。
mybatis-config.xml中配置类型别名
mybatis-config.xml中引入 db.properties
注册绑定我们的mapper.xml文件,有四种配置方式
方式一(推荐使用)
方式二(不建议使用)
方式三
注:
1. 接口及其mapper配置文件必须同名
2.接口及其mapper配置文件必须在同一个包中
方式四
不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。
依赖注入框架可以创建线程安全的、基于事务的 SqlSession 和映射器,并将它们直接注入到你的 bean 中,因此可以直接忽略它们的生命周期。
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 最佳作用域是方法作用域(也就是局部方法变量)。
可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。
一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。最佳作用域是应用作用域。
可以使用单例模式或者静态单例模式。
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的.。 最佳的作用域是请求或方法作用域。
绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。
MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。
SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):
cache
– 该命名空间的缓存配置。cache-ref
– 引用其它命名空间的缓存配置。resultMap
– 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。parameterMap
– 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。sql
– 可被其它语句引用的可重用语句块。insert
– 映射插入语句。update
– 映射更新语句。delete
– 映射删除语句。select
– 映射查询语句。查询语句是 MyBatis 中最常用的元素之一——光能把数据存到数据库中价值并不大,还要能重新取出来才有用,多数应用也都是查询比修改要频繁。 MyBatis 的基本原则之一是:在每个插入、更新或删除操作之间,通常会执行多个查询操作。因此,MyBatis 在查询和结果映射做了相当多的改进。一个简单查询的 select 元素是非常简单的。例:
语句名为 getUserById,接受一个 int(或 Integer)类型的参数,并返回一个 User类型的对象,其中的键是列名,值便是结果行中的对应值。
数据变更语句 insert,update 和 delete 的实现非常接近 例:
insert into taotao.user values (#{id},#{name},#{age})
insert into taotao.user values (#{userId},#{userName},#{userAge})
update taotao.user set age = #{age},name=#{name} where id = #{id}
update taotao.user
name = #{userName,jdbcType=VARCHAR},
age = #{userAge,jdbcType=INTEGER}
where id = #{userId,jdbcType=TINYINT};
delete from user where id=#{id}
如果你的数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server),那么你可以设置 useGeneratedKeys=”true”,然后再把 keyProperty 设置为目标属性就 OK 了。例如,如果上面的 Author 表已经在 id 列上使用了自动生成,那么语句可以修改为:
insert into user (name,age) values (#{name},#{age})
.默认情况下,使用 #{}
参数语法时,MyBatis 会创建 PreparedStatement
参数占位符,并通过占位符安全地设置参数(就像使用 ? 一样)。
不过有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串。 比如 ORDER BY 子句,这时候你可以使用${}:,这样,MyBatis 就不会修改或转义该字符串了。
ORDER BY ${columnName}
当 SQL 语句中的元数据(如表名或列名)是动态生成的时候,字符串替换将会非常有用。例如:
@Select("select * from user where id = #{id}")
User findById(@Param("id") long id);
@Select("select * from user where name = #{name}")
User findByName(@Param("name") String name);
@Select("select * from user where email = #{email}")
User findByEmail(@Param("email") String email);
可以替换成:
@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);
其中 ${column}
会被直接替换,而 #{value}
会使用 ?
预处理。
注:用这种方式接受用户的输入,并用作语句参数是不安全的,会导致潜在的 SQL 注入攻击。
可以解决数据库字段名和实例属性不一致情况。
实例:
public class User {
private int id;
private String userName;
private int age;
...
}
结果映射:(resultMap)
create table teacher(
tid int(10) NOT NULL AUTO_INCREMENT PRIMARY KEY,
tname VARCHAR(30) NOT NULL
)ENGINE=INNODB DEFAULT CHARSET=utf8;
--drop table teacher;
insert into teacher values (1,'张老师');
create table student(
sid int(10),
sname varchar(20),
age int(3),
gender varchar(10),
teacherid int(10),
constraint fktid foreign key(teacherid) references teacher(tid)
)ENGINE=INNODB DEFAULT CHARSET=utf8;
insert into student values (1,'张三',18,'男',1);
insert into student values (2,'公孙倩影',18,'男',1);
insert into student values (3,'赵云龙',18,'男',1);
insert into student values (4,'李雪',17,'女',1);
select * from student;
--show tables;
package com.xx.entity;
public class Student {
private int sid;
private String sname;
private int age;
private String gender;
private Teacher teacher;
public Student() {
}
public Student(int sid, String sname, int age, String gender, Teacher teacher) {
this.sid = sid;
this.sname = sname;
this.age = age;
this.gender = gender;
this.teacher = teacher;
}
public int getSid() {
return sid;
}
public void setSid(int sid) {
this.sid = sid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public Teacher getTeacher() {
return teacher;
}
public void setTeacher(Teacher teacher) {
this.teacher = teacher;
}
@Override
public String toString() {
return "Student{" +
"sid=" + sid +
", sname='" + sname + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
", teacher=" + teacher +
'}';
}
}
package com.xx.entity;
public class Teacher {
private int tid;
private String tname;
public Teacher() {
}
public Teacher(int tid, String tname) {
this.tid = tid;
this.tname = tname;
}
public int getTid() {
return tid;
}
public void setTid(int tid) {
this.tid = tid;
}
public String getTname() {
return tname;
}
public void setTname(String tname) {
this.tname = tname;
}
@Override
public String toString() {
return "Teacher{" +
"tid=" + tid +
", tname='" + tname + '\'' +
'}';
}
}
package com.xx.mapper;
import com.xx.entity.Student;
import java.util.List;
public interface StudentMapper {
public List selectStudents();
public List getStudents();
}
package com.xx.mapper;
import com.xx.entity.Teacher;
import org.apache.ibatis.annotations.Param;
public interface TeacherMapper {
public Teacher selectTeacherById(@Param("tid") int id);
}
package com.xx.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 sessionFactory;
static{
String resource="mybatis-config.xml";
try {
InputStream inputStream = Resources.getResourceAsStream(resource);
sessionFactory=new SqlSessionFactoryBuilder().build(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
}
public static SqlSession getSqlSession(){
return sessionFactory.openSession();
}
}
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&allowMultiQueries=true&serverTimezone=UTC
username=root
password=root
.。。
package com.xx.entity;
public class Student {
private int sid;
private String sname;
private int age;
private String gender;
private int stid;
public Student() {
}
public Student(int sid, String sname, int age, String gender, int stid) {
this.sid = sid;
this.sname = sname;
this.age = age;
this.gender = gender;
this.stid = stid;
}
public int getSid() {
return sid;
}
public void setSid(int sid) {
this.sid = sid;
}
public String getSname() {
return sname;
}
public void setSname(String sname) {
this.sname = sname;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getTeacherid() {
return stid;
}
public void setTeacherid(int teacherid) {
this.stid = teacherid;
}
@Override
public String toString() {
return "Student{" +
"sid=" + sid +
", sname='" + sname + '\'' +
", age=" + age +
", gender='" + gender + '\'' +
", stid=" + stid +
'}';
}
}
package com.xx.entity;
import java.util.List;
public class Teacher {
private int tid;
private String tname;
private List students;
public Teacher() {
}
public Teacher(int tid, String tname, List students) {
this.tid = tid;
this.tname = tname;
this.students = students;
}
public int getTid() {
return tid;
}
public void setTid(int tid) {
this.tid = tid;
}
public String getTname() {
return tname;
}
public void setTname(String tname) {
this.tname = tname;
}
public List getStudents() {
return students;
}
public void setStudents(List students) {
this.students = students;
}
@Override
public String toString() {
return "Teacher{" +
"tid=" + tid +
", tname='" + tname + '\'' +
", students=" + students +
'}';
}
}
根据不同的标签生成需要的sql
根据条件选择其中一个条件执行,类似java 的 switch case语句
mapper示例
where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
如果 where 元素与期望的不太一样,可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:
...
prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。
用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:
update student
sname = #{sname,jdbcType=INTEGER},
age = #{age,jdbcType=VARCHAR},
gender = #{gender,jdbcType=VARCHAR},
teacherid = #{teacherid,jdbcType=INTEGER}
where sid = #{sid}
对集合遍历,可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。