简介
Mybatis是一个ORM框架,ORM(Object relational Mapping)是对象关系映射模型,就是数据库的表和简单的java对象之间的映射。主要解决数据库数据和POJO对象的相互映射。通过这层映射关系可以简单迅速的把数据库表的数据转换为POJO。
相对于其他的ORM,如Hibernate,Mybtis是一个半自动映射的框架,因为需要手工的匹配POJO,SQL和映射关系。而全自动的Hibernate只需要提供POJO和映射关系。
ORM映射模型:
java应用程序 ==> 映射配置 ===》数据库
Mybatis的映射文件由以下三个部分组成:
SQL、映射规则、POJO
Mybatis的映射模型:
Mybatis应用程序 ==> POJO ==> 接口、注解、sqlMapper、映射文件 ==> 数据库
Mybatis的运行分为两大部分:一块是读取配置文件缓存到Configuration类中,用来创建SqlSessionFactory,第二块是SqlSession的执行过程。
传统的JDBC编程
1.使用JDBC编程需要连接数据库,注册驱动和数据库信息
2.操作Connection,打开Statement对象
3.通过Statement对象执行sql,返回结果到Resultset对象
4.使用ResultSet读取数据,通过代码转换为POJO对象
5.关闭数据库相关资源
可以看出以下一个简单的查询就需要这么多的代码,如果更为复杂的应用、事务处理等等,ORM也正是基于JDBC的封装,简化我们编程。
public class JDBCDemo {
public static void main(String[] args) {
JDBCDemo jdbcDemo = new JDBCDemo();
Role role = jdbcDemo.getRole(1);
System.out.println(role.id + " " + role.roleName + " " + role.note);
}
private Connection getConnection() {
Connection connection = null;
try {
Class.forName("com.mysql.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/mybatis";
String user = "root";
String pwd = "123456";
connection = DriverManager.getConnection(url, user, pwd);
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}
return connection;
}
public Role getRole(int id) {
Connection connection = getConnection();
ResultSet rs = null;
PreparedStatement ps = null;
try {
ps = connection.prepareStatement("select id,role_name,note from t_role where id = ?");
ps.setInt(1, id);
rs = ps.executeQuery();
while (rs.next()) {
int id1 = rs.getInt("id");
String roleName = rs.getString("role_name");
String note = rs.getString("note");
Role role = new Role();
role.id = id1;
role.roleName = roleName;
role.note = note;
return role;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
this.close(rs, ps, connection);
}
return null;
}
private void close(ResultSet rs, PreparedStatement ps, Connection connection) {
try {
if (rs != null && !rs.isClosed()) {
rs.close();
}
if (ps != null && !ps.isClosed()) {
ps.close();
}
if (connection != null && !connection.isClosed()) {
connection.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
class Role {
int id;
String roleName;
String note;
}
}
使用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">
<configuration>
<!--定义数据库信息-->
<environments default="development">
<environment id="development">
<!--定义事务管理器-->
<transactionManager type="JDBC"></transactionManager>
<!--定义数据库连接-->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!--定义映射器-->
<mappers>
<mapper resource="mapper/TRoleMapper.xml"></mapper>
</mappers>
</configuration>
映射规则文件 TRoleMapper.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="com.example.demo.mapper.TRoleMapper">
<select id="getRoleById" parameterType="int" resultType="com.example.demo.base.Role">
select id,role_name as roleName,note from t_role where id = #{id};
</select>
</mapper>
Mapper TRoleMapper.java
public interface TRoleMapper {
Role getRoleById(int id);
}
Mybatis 应用程序、测试类
public class MybatisDemo {
static SqlSessionFactory sqlSessionFactory = null;
public static void main(String[] args) {
SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();
SqlSession sqlSession = sqlSessionFactory.openSession();
TRoleMapper mapper = sqlSession.getMapper(TRoleMapper.class);
Role roleById = mapper.getRoleById(1);
System.out.println("roleName : " + roleById.getRoleName());
}
static SqlSessionFactory getSqlSessionFactory() {
if (null == sqlSessionFactory) {
try {
String resource = "config.xml";
sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream(resource));
return sqlSessionFactory;
} catch (Exception e) {
e.printStackTrace();
}
}
return sqlSessionFactory;
}
}
Mybatis 基本构成
核心组件有:
SqlSessionFactoryBuilder(构造器):根据配置信息或者代码来生成SqlSessionFactory(工厂接口)。
SqlSessionFactory:依靠工厂来生成SqlSession(会话)。
SqlSession:是一个即可以发送SQL去执行并返回结果,也可以获取mapper的接口。类似以JDBC的Connection对象。
Sql Mapper:由一个java接口和xml文件(或者注解)构成,需要给出对应的sql和映射规则,负责发送sql去执行,并返回结果。
执行过程:
SqlSessionFactoryBuilder =》SqlSessionFactory=>SqlSession=>数据库
SqlSessionFactoryBuilder =》SqlSessionFactory=>SqlSession=>SQL Mapper=>数据库
SqlSessionFactory类
Mybatis应用都是以SqlSessionFactory的实例为中心,通过SqlSessionFactoryBuilder获得,可以通过xml配置方式获取,也可以通过硬编码方式获取SqlSessionFactory,主要方法有获取SqlSession、获取Configuration类。
默认的实现类是DefaultSqlSessionFactory
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);
Configuration getConfiguration();
}
Configuration 类
Configuration存在Mybatis应用的整个生命周期中,以便于重复的读取和使用。在应用启动时候解析XML配置文件,保存到Configuration类中。
如: protected final Map<String, MappedStatement> mappedStatements
key为mapper的名称+方法名称,为后续的动态代理执行过程中查找相应的MappedStatement
SqlSession类
SqlSession也是一个接口类,真正的执行是Executor接口。
public interface SqlSession extends Closeable {
<T> T selectOne(String statement);
<T> T selectOne(String statement, Object parameter);
<E> List<E> selectList(String statement);
<E> List<E> selectList(String statement, Object parameter);
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
<K, V> Map<K, V> selectMap(String statement, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
<T> Cursor<T> selectCursor(String statement);
<T> Cursor<T> selectCursor(String statement, Object parameter);
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
void select(String statement, Object parameter, ResultHandler handler);
void select(String statement, ResultHandler handler);
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
int insert(String statement);
int insert(String statement, Object parameter);
int update(String statement);
int update(String statement, Object parameter);
int delete(String statement);
int delete(String statement, Object parameter);
void commit();
void commit(boolean force);
void rollback();
void rollback(boolean force);
List<BatchResult> flushStatements();
@Override
void close();
void clearCache();
Configuration getConfiguration();
<T> T getMapper(Class<T> type);
Connection getConnection();
}
Mybatis底层使用到了反射、动态代理等技术。
JDK动态代理:
一个Demo
public interface HelloService {
public void sayHello(String name);
}
public class HelloServiceImpl implements HelloService {
@Override
public void sayHello(String name) {
System.out.println("hello " + name);
}
}
public class HelloServiceProxy implements InvocationHandler {
private Object target;
public Object bind(Object target) {
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理类方法开始执行》》》》》》》》》");
System.out.println("执行前》》》》》》》");
Object invoke = method.invoke(target, args);
System.out.println("执行后》》》》》》》");
return invoke;
}
}
public class JDKDynamicProxyDemo {
public static void main(String[] args) {
HelloServiceProxy helloServiceProxy = new HelloServiceProxy();
HelloService proxy = (HelloService) helloServiceProxy.bind(new HelloServiceImpl());
proxy.sayHello("xxxxxx");
}
}
运行结果
代理类方法开始执行》》》》》》》》》
执行前》》》》》》》
hello xxxxx
执行后》》》》》》》
Mapper的动态代理
MapperProxyFactory
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethodInvoker> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
可以看出这里对接口的绑定。代理的方法在MapperProxy中
MapperProxy#invoke()
首先会判断是否是一个类,mapper是一个接口,判断为false生成MapperMethod对象,然后执行execute方法,把sqlsession和当前运行的参数传递进去。
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -4724728412955527868L;
private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
private static final Constructor<Lookup> lookupConstructor;
private static final Method privateLookupInMethod;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
static {
Method privateLookupIn;
try {
privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
} catch (NoSuchMethodException e) {
privateLookupIn = null;
}
privateLookupInMethod = privateLookupIn;
Constructor<Lookup> lookup = null;
if (privateLookupInMethod == null) {
// JDK 1.8
try {
lookup = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
lookup.setAccessible(true);
} catch (NoSuchMethodException e) {
throw new IllegalStateException(
"There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.",
e);
} catch (Exception e) {
lookup = null;
}
}
lookupConstructor = lookup;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
// A workaround for https://bugs.openjdk.java.net/browse/JDK-8161372
// It should be removed once the fix is backported to Java 8 or
// MyBatis drops Java 8 support. See gh-1929
MapperMethodInvoker invoker = methodCache.get(method);
if (invoker != null) {
return invoker;
}
return methodCache.computeIfAbsent(method, m -> {
if (m.isDefault()) {
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
private MethodHandle getMethodHandleJava9(Method method)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
final Class<?> declaringClass = method.getDeclaringClass();
return ((Lookup) privateLookupInMethod.invoke(null, declaringClass, MethodHandles.lookup())).findSpecial(
declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()),
declaringClass);
}
private MethodHandle getMethodHandleJava8(Method method)
throws IllegalAccessException, InstantiationException, InvocationTargetException {
final Class<?> declaringClass = method.getDeclaringClass();
return lookupConstructor.newInstance(declaringClass, ALLOWED_MODES).unreflectSpecial(method, declaringClass);
}
interface MapperMethodInvoker {
Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
}
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
public PlainMethodInvoker(MapperMethod mapperMethod) {
super();
this.mapperMethod = mapperMethod;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
}
private static class DefaultMethodInvoker implements MapperMethodInvoker {
private final MethodHandle methodHandle;
public DefaultMethodInvoker(MethodHandle methodHandle) {
super();
this.methodHandle = methodHandle;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return methodHandle.bindTo(proxy).invokeWithArguments(args);
}
}
}
MapperMethod#execute()
MapperMethod采用命令模式,会跳转到不同的方法执行
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
}
Mybatis-Spring
SpringAop
以角色服务类(RoleServiceImpl)为例:
Spring为RoleServiceImpl生成代理类,这样调用insertRole方法的时候进入一个invoke方法里面。spring会判断是否要拦截这个方法,这是一个切入点配置的问题,通过正则表达式配置,比如正则配置 insert*这样的通配,那么Spring就会拦截这个方法,否则不拦截,直接通过invoke方法反射调用这个方法,就结束了。
其次是切面,当插入角色,包含了事务,这个事务就是整个方法的一个切面。可能这个方法比较复杂,包含了很多个操作,但是它们都受同一个事物管理,那么事务就是这方法的一个切面,这个时候Spring会根据我们的配置信息,知道这个方法需要事务,传播行为,回滚异常 等等。
再次就是连接点,连接点根据在程序中根据不同的通知来实现的程序段。由于Spring采用了动态代理,使得可以在反射原始的方法之前做一些事情,于是有了前置通知(Before Advice)在反射之后做一些事情(After Adivce),反射原来方法可能正常返回,也可能抛出异常,就有了异常后通知(After throwing Adivce),也有可能用自定义方法 取代原有的方法,不采用原有的invoke方法,而使用自定义方法,所以还有环绕通知(Around Advice)。
代理目标,就是哪个类对象被代理了,显然这里是RoleServiceImpl对象被代理了。
Aop代理(Aop Proxy)就是指采用何种方式来代理,我们知道JDK的代理需要使用接口,而CGLIB不需要,Spring默认采用这样的规则,当Sping的服务包含接口描述时采用JDK动态代理,否则采用CGLIB代理。当然可以通过配置修改。
事务的隔离级别:
脏读 ==》 读写提交 ==》可重复读 ==》序列化
Spring定义了7中传播行为:
传播行为,指方法之间的调用问题。大部分情况下,我们认为事务应该都是一次性全部成功或者全部失败。但是有一些特例,不需要回滚全部,需要回滚部分的情况,就需要方法直接使用独立的事务控制,即这里设置不同的传播级别。
Spring默认的传播级别:
PROPAGETION_REQUIRED :如果存在一个事务,则支持当前事务,如果没有则开启
PROPAGETION_SUPPORTS: 如果存在一个事务,则支持当前事务,如果没有
PROPAGATION_MANDATORY:如果已经存在一个事务,则支持当前事务,如果没有一个活动事务,则抛出异常
PROPAGATION_REQUERY_NEW:总是开启一个新事务,如果一个事务已经存在,则将这个存在的事务挂起
PROPAGATION_NOT_SUPPORT:总是以非事务的方式执行,如果存在一个活动事务,则抛出异常
PROPAGETION_NEVER:总是以非事务的方式执行 ,如果存在一个事务,则抛出异常
PROPAGATION_NESTED : 如果一个活动的事务存在,则运行在一个嵌套事务中,如果没有活动的事务,则按照默认事务属性执行
网友评论