什么是Mybatis
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
Mybatis优势
- 与JDBC相比,减少了50%以上的代码量。
- MyBatis实现了接口绑定,无需自己写DAO实现类.
- MyBatis是最简单的持久化框架,小巧并且简单易学。
- MyBatis相当灵活,不会对应用程序或者数据库的现有设计强加任何影响,SQL写在XML里,从程序代码中彻底分离,降低耦合度,便于统一管理和优化,并可重用
Mybatis简单实现
- Mybatis主要是为了简化我们操纵数据库的过程,那么首先应该在本地的数据库建立一张表。
CREATE TABLE STUDENT (
id INT,
name VARCHAR(20),
PRIMARY KEY (id)
);
- 向表中插入记录。
INSERT INTO student (id,name) VALUES (1,'A');
INSERT INTO student (id,name) VALUES (2,'B');
INSERT INTO student (id,name) VALUES (3,'C');
INSERT INTO student (id,name) VALUES (4,'D');
INSERT INTO student (id,name) VALUES (5,'E');
INSERT INTO student (id,name) VALUES (6,'F');
INSERT INTO student (id,name) VALUES (7,'F');
INSERT INTO student (id,name) VALUES (8,'G');
INSERT INTO student (id,name) VALUES (9,'H');
INSERT INTO student (id,name) VALUES (10,'I');
- 在IDEA中新建一个maven工程,并加入使用Mybatis相关依赖。
<!-- 与连接数据库相关的依赖 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.20</version>
</dependency>
<!-- 与Mybatis相关的依赖 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.0</version>
</dependency>
- 编写domain类,此类中的属性与数据库中的列名一一对应。
package com.alibaba.mybatis.domain;
public class Student {
private Integer id;
private String name;
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;
}
@Override
public String toString() {
return "[" + "id:" + id + " name:" + name + "]";
}
}
- 编写dao层接口,不需要编写该接口的实现类。具体的原因会在下面进行具体的解释。这也是使用Mybatis后最大的困惑。
package com.alibaba.mybatis.mapper;
import com.alibaba.mybatis.domain.Student;
import java.util.List;
public interface StudentMapper {
List<Student> findAll();
}
- 在这里我们要实现的是查询数据库的功能,因此,我们需要编写dao层的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">
<mapper namespace="com.alibaba.mybatis.mapper.StudentMapper">
<select id="findAll" resultType="Student">
select * from student
</select>
</mapper>
- 配置mybatis-config文件,该文件的具体的路径为:src/main/resources。
<?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>
<!-- 类型的别名 -->
<typeAliases>
<package name="com.alibaba.mybatis.domain"/>
</typeAliases>
<!-- 配置Mybatis运行环境 -->
<environments default="development">
<environment id="development">
<!-- type="JDBC" 代表使用JDBC的提交和回滚来管理事务 -->
<transactionManager type="JDBC"/>
<!-- mybatis提供了3种数据源类型,分别是:POOLED,UNPOOLED,JNDI -->
<!-- POOLED 表示支持JDBC数据源连接池 -->
<!-- UNPOOLED 表示不支持数据源连接池 -->
<!-- JNDI 表示支持外部数据源连接池 -->
<dataSource type="POOLED">
<!-- 数据库的具体配置 -->
<!-- url表示你数据库的地址
username表示的是数据库的用户
password表示的是数据库的密码-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/hongchen"/>
<property name="username" value="root"/>
<property name="password" value=""/>
</dataSource>
</environment>
</environments>
<!-- 注册StudentMapper.xml映射文件 -->
<mappers>
<package name="com.alibaba.mybatis.mapper"/>
</mappers>
</configuration>
- 编写测试类
package com.alibaba.mybatis;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class DBTools {
public static SqlSessionFactory sessionFactory;
static{
try {
//使用MyBatis提供的Resources类加载mybatis的配置文件
Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
//构建sqlSession的工厂
sessionFactory = new SqlSessionFactoryBuilder().build(reader);
} catch (Exception e) {
e.printStackTrace();
}
}
//创建能执行映射文件中sql的sqlSession
public static SqlSession getSession(){
return sessionFactory.openSession();
}
}
package com.alibaba.mybatis;
import com.alibaba.mybatis.domain.Student;
import com.alibaba.mybatis.mapper.StudentMapper;
import org.apache.ibatis.session.SqlSession;
import java.util.List;
public class Demo {
public static void main(String[] args) {
selectAllUser();
}
/**
* 查询所有的用户
*/
private static void selectAllUser(){
SqlSession session=DBTools.getSession();
StudentMapper mapper=session.getMapper(StudentMapper.class);
try {
List<Student> user=mapper.findAll();
System.out.println(user.toString());
session.commit();
} catch (Exception e) {
e.printStackTrace();
session.close();
}
}
}
执行Demo类中的main函数,得到如下的结果:
Mybatis技术内幕
在学习Mybatis过程中遇到一个一直困扰我很久的问题,在Mybatis中声明一个interface接口,没有编写任何实现类,Mybatis就能返回接口实例,并调用接口方法返回数据库中的数据。实现这个功能的底层原理主要是底层采用了jdk动态代理。
动态代理
动态代理的主要功能是:通过拦截器方法回调,对目标target方法进行增强。言外之意就是为了增强目标target方法,这句话确实是讲的没有错误,但是在动态代理的过程中我们也可以做到连目标tagert都不要的霸权。
自定义自动映射器Mapper
首先我们先不研究源码,通过仿照源码的格式来自定义我们的自动映射器。其中我们的domain类还是采用的上面的Student类,不过在原来的基础上加上一个带参构造器。
public class Student {
private Integer id;
private String name;
public Student(Integer id,String name) {
this.id=id;
this.name=name;
}
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;
}
@Override
public String toString() {
return "[" + "id:" + id + " name:" + name + "]";
}
}
dao层接口中的方法改为getStudentById()。
public interface StudentMapper {
public Student getStudentById(Integer id);
}
实现一个InvocationHandler。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class MapperProxy implements InvocationHandler {
public <T> T newInstance(Class<T> clz) {
return (T) Proxy.newProxyInstance(clz.getClassLoader(), new Class[] { clz }, this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
// 诸如hashCode()、toString()、equals()等方法,将target指向当前对象this
return method.invoke(this, args);
} catch (Throwable t) {
}
}
return new Student((Integer) args[0], "hongchen.zhx");
}
}
在MapperProxy类中,我们可以看到在执行Object对象的方法时,target被指向了this,此时target已经可以说是成为一个傀儡了。已经没有target什么事了。
编写测试代码进行测试。
public class Demo {
public static void main(String[] args) {
MapperProxy proxy = new MapperProxy();
StudentMapper mapper = proxy.newInstance(StudentMapper.class);
Student student= mapper.getStudentById(1001);
System.out.println("ID:" + student.getId());
System.out.println("Name:" + student.getName());
System.out.println(mapper.toString());
}
}
那么测试代码运行后的结果为:
真正的MapperProxy
通过以上自定义的Mapper映射器再来看相对应的源码就会轻松很多。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
//在执行诸如Object的方式时,target指向当前对象this。
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
//从缓存中获取MapperMethod对象,如果缓存中没有,则创建新的MapperMethod对象并添加到缓存中。
final MapperMethod mapperMethod = cachedMapperMethod(method);
//调用MapperMethod.execute()方法执行sql语句。
return mapperMethod.execute(sqlSession, args);
}