一、什么是 MyBatis?
直接看官方文档:mybatis.org/mybatis-3/z…
从上面我们了解到:
1、MyBatis 是一款优秀的持久层框架
2、MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
3、MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
二、什么是JDBC?
原文:www.cnblogs.com/erbing/p/58…
1、简介
JDBC:Java Data Base Connectivity,即java数据库连接,是一种用于执行SQL语句的Java API。
2、数据驱动
我们安装好数据库之后,我们的应用程序也是不能直接使用数据库的,必须要通过相应的数据库驱动程序,通过驱动程序去和数据库打交道。其实也就是数据库厂商的JDBC接口实现,即对Connection等接口的实现类的jar文件。

3、常用接口
(1) Driver接口
Driver接口由数据库厂家提供,在编程中要连接数据库,必须先装载特定厂商的数据库驱动程序,不同的数据库有不同的装载方法。如:
装载MySql驱动:Class.forName("com.mysql.jdbc.Driver");
装载Oracle驱动:Class.forName("oracle.jdbc.driver.OracleDriver");
(2)、Connection接口
Connection与特定数据库的连接(会话),在连接上下文中执行sql语句并返回结果。通过DDriverManager.getConnection(url, user, password)方法建立连接
连接MySql数据库:
Connection conn = DriverManager.getConnection("jdbc:mysql://host:port/database", "user", "password");
(3)、Statement接口 用于执行静态SQL语句并返回它所生成结果的对象。
常用方法:
execute(String sql):运行语句,返回是否有结果集
executeQuery(String sql):运行select语句,返回ResultSet结果集。
executeUpdate(String sql):运行insert/update/delete操作,返回更新的行数。
addBatch(String sql) :把多条sql语句放到一个批处理中。
executeBatch():向数据库发送一批sql语句执行。
(4)、ResultSet接口 提供检索不同类型字段的方法:
getString(int index)、getString(String columnName):获得在数据库里是varchar、char等类型的数据对象。
getFloat(int index)、getFloat(String columnName):获得在数据库里是Float类型的数据对象。
getDate(int index)、getDate(String columnName):获得在数据库里是Date类型的数据。
getBoolean(int index)、getBoolean(String columnName):获得在数据库里是Boolean类型的数据。
getObject(int index)、getObject(String columnName):获取在数据库里任意类型的数据。
4、JDBC的使用
从上面常用的API中,我们就可以知道使用JDBC的步骤如下:
加载JDBC驱动程序 → 建立数据库连接Connection → 创建执行SQL的语句Statement → 处理执行结果ResultSet → 释放资源
//建立连接
Connection conn = DriverManager.getConnection(url, user, password);
//生成Statement对象,并执行SQL语句
String sql = "delete from table where id=" + id;
Statement st = conn.createStatement();
//使用结果集保存结果
ResultSet rs = st.executeQuery();
While(rs.next()){
rs.getString(“col_name”);
rs.getInt(1);
//…
}
//释放资源try..catch..finally
if(st!=null) st.colse();
if(conn!=null) conn.close();
三、MyBatis与数据库
1、程序员,MyBatis与数据库的关系
程序员要通过MyBatis来操作数据库。
2、首先我们必须告诉MyBatis要怎么操作数据库?
可以通过XML配置文件或者注解的方式
MyBatis提供了一个类Configuration, Mybatis读取XML配置文件后会将内容放在一个Configuration类中,Configuration类会存在整个Mybatis生命周期,以便重复读取。
3、想要Mybatis与数据库打交道,就要有一个类似于JDBC的Connection对象,在MyBatis中叫SqlSesion,所以我们要有一个SqlSession。
Mybatis 读取XML配置文件后会将内容放在一个Configuration类中,SqlSessionFactoryBuilder会读取Configuration类中信息创建SqlSessionFactory。由SqlSessionFactory创建SqlSession。
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//读取Configuration类中信息
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession=null;
try{
//创建SqlSession
sqlSession=sqlSessionFactory.openSession();
//逻辑处理代码
...
//提交
sqlSession.commit();
} catch(Exception ex){
// 异常回滚
sqlSession.roolback();
} finally{
if(sqlSession!=null){
sqlSession.close();
}
}
4、刚刚说SqlSession类似于JDBC的Connection对象,那能干什么
① 获取对应的Mapper,让映射器通过命名空间和方法名称找到对应的SQL,发送给数据库执行后返回结果。
RoleMapper roleMapper = sqlSession.getMapper(RoleMapper.class);
Role role = roleMapper.getRole(1L);
②直接使用SqlSession,通过命名信息去执行SQL返回结果,该方式是IBatis版本留下的,SqlSession通过Update、Select、Insert、Delete等方法操作。
Role role = (Role)sqlSession.select("com.mybatis.mapper.RoleMapper.getRole",1L);
实际上,Mybatis底层利用JDK动态代理技术实现该接口,底层最后还是使用的IBatis中SqlSession通过Update、Select、Insert、Delete等方法操作。
5、什么是动态代理?
可以在不修改别代理对象代码的基础上,通过扩展代理类,进行一些功能的附加与增强。
怎么使用
(1)、首先有一个接口Calculate,只有一个add(int a,int b) (2)、然后是接口的实现类CalculateImp,实现add(int a,int b) (3)、代理类实现InvocationHandler
public class CalculateProxy implements InvocationHandler {
private Object target;
//总要让我知道要代理谁吧:构造方法中把传入一个代理类的实例
public CalculateProxy(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("====== Before() ======");
//执行被代理类的原方法
method.invoke(target, args);
System.out.println("====== After () ======");
return null;
}
}
(4)、拿到代理对象,操作接口方法
public class test {
public static void main(String[] args) {
//构造方法传入被代理类实例
InvocationHandler handler = new CalculateProxy(new CalculateImp());
//生成代理对象
Calculate calculateProxy =
(Calculate) Proxy.newProxyInstance(Calculate.class.getClassLoader(),
new Class[]{Calculate.class},
handler);
//执行被代理类方法
calculateProxy.add(10,20);
}
}
6、但是在MyBatis中,我们只写了接口,并没有写实现类阿,那怎么实现动态代理?
例如:
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(1);
借鉴原文:www.cnblogs.com/demingblog/…
其实,从本质上来讲,我们操作数据库主要是通过SQL语句,那么只要找到SQL语句然后执行不就可以
上面的 mapper 可以调用selectBlog(1) 这个方法,说明 mapper是个对象,因为对象才具有方法行为实现啊。BlogMapper接口是不能实例化的,更没有具体方法实现。我们并没有定义一个类,让它实现BlogMapper接口,而在这里它只是通过调用session.getMapper() 所得到的。
由此,我们可以推断:肯定是session.getMapper() 方法内部产生了BlogMapper的实现类。有什么技术可以根据BlogMapper 接口生成了一个实现类呢?就是动态代理。
我们既然能够从SqlSession中得到BlogMapper接口的,那么我们肯定需要先在哪里把它放进去了,然后 SqlSession 才能生成我们想要的代理类啊。我们可以从getMapper()联想,可能会有一个setMapper()或者addMapper()方法。确实是有!
configuration.addMapper(BlogMapper.class);
查看实现
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
看到这里 mapper 实际上被添加到 mapperRegistry (mapper注册器)中。
public class MapperRegistry {
private final Map<Class<?>, MapperProxyFactory<?>>
knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) { // 只添加接口
if (hasMapper(type)) { // 不允许重复添加
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type)); // 注意这里
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
...
}
}
首先看到MapperRegistry类,有一个私有属性knowMappers,它是一个HashMap。其Key为当前Class对象,value为一个MapperProxyFactory实例
在MapperRegistry类的addMapper()方法中
knownMappers.put(type, new MapperProxyFactory<T>(type));
相当于把:诸如BlogMapper之类的Mapper接口被添加到了MapperRegistry中的一个HashMap中。并以 Mapper 接口的 Class 对象作为 Key , 以一个携带Mapper接口作为属性的MapperProxyFactory实例作为value。MapperProxyFactory从名字来看,好像是一个工厂,用来创建MapperProxy的工厂。
所以,我们可以从HashMap取出对象的MapperProxyFactory
BlogMapper mapper = session.getMapper(BlogMapper.class);
我们跟踪一下session.getMapper() 方法的代码实现,这里 SqlSession 是一个接口,他有两个实现类,
一个是DefaultSqlSession,另外一个是SqlSessionManager,
这里我们用的是DefaultSqlSession. 为什么是DefaultSqlSession呢?
因为我们在初始化SqlSessionFactory的时候所调用的SqlSessionFactoryBuilder的build()方法里边配置的就是DefaultSqlSession,
我们进入到DefaultSession类中
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
@Override
public <T> T getMapper(Class<T> type) {
//最后会去调用MapperRegistry.getMapper
return configuration.<T>getMapper(type, this);
}
}
此前我们已经知道,MapperRegistry是存放了一个HashMap的,我们继续跟踪进去看看,那么这里的get,肯定是从这个hashMap中取数据。
public class MapperRegistry {
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();// Mapper 映射
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory =
(MapperProxyFactory<T>) knownMappers.get(type);
try {
return mapperProxyFactory.newInstance(sqlSession); // 重点看这里
} catch (Exception e) {
}
}
}
我们调用的session.getMapper(BlogMapper.class);最终会到达上面这个方法,这个方法,根据BlogMapper的class对象,以它为key在knowMappers中找到了对应的value—— MapperProxyFactory(BlogMapper) 对象,然后调用这个对象的newInstance()方法。知道反射的人都知道,这个方法肯定是创造了一个对象。
public class MapperProxyFactory<T> { //映射器代理工厂
private final Class<T> mapperInterface;
private Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
// 删除部分代码,便于阅读
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
//使用了JDK自带的动态代理生成映射器代理类的对象
return (T) Proxy.newProxyInstance(
mapperInterface.getClassLoader(),
new Class[] { mapperInterface },
mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
看到这里,就清楚了,最终是通过Proxy.newProxyInstance产生了一个BlogMapper的代理对象。Mybatis 为了完成 Mapper 接口的实现,运用了代理模式。具体是使用了JDK动态代理。
代理模式中,代理类(MapperProxy)中才真正的完成了方法调用的逻辑。
// 实现了InvocationHandler
public class MapperProxy<T> implements InvocationHandler, Serializable {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理以后,所有Mapper的方法调用时,都会调用这个invoke方法
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args); // 注意1
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method); // 使用了缓存
//执行CURD,最终就会执行增删改查了
return mapperMethod.execute(sqlSession, args); // 注意2
}
}
再往下一层,就是执行JDBC那一套了,获取链接,执行,得到ResultSet,解析ResultSet映射成JavaBean。
四、总结
1、Mybatis 读取XML配置文件后会将内容放在一个Configuration类中,SqlSessionFactoryBuilder会读取Configuration类中信息创建SqlSessionFactory。
2、在初始化SqlSessionFactory时,Mapper 接口进行注册,注册在了名为 MapperRegistry 类的 HashMap中,key = Mapper class, value = 创建当前Mapper的工厂。
3、SqlSessionFactory创建SqlSession。
4、SqlSession中可以通过getMapper()拿到代理对象,SqlSession.getMapper 运用了 JDK动态代理,产生了目标Mapper接口的代理对象。
5、动态代理的代理类是 MapperProxy ,这里边mapperMethod.execute(sqlSession, args)最终完成了增删改查方法的调用。