MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射,
MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
mybatis核心流程三大阶段:
1、初始化阶段
读取xml配置文件和注解中的配置信息,创建配置对象,并完成各个模块的初始化的工作。
读取mybatis配置文件,创建SqlSessionFactroy
String resource = "mybatis/mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
SqlSessionFactory factroy = new SqlSessionFactoryBuilder().build(is);
is.close();
2、代理阶段
封装iBatis的编程模型。使用mapper接口开发的初始化工作
获取sqlSession
SqlSession sqlSession = factroy.openSession();
3、获取对应mapper
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
3、数据读写阶段
遵循JDBC规范,通过SqlSession完成SQL的解析,参数的映射、SQL的执行、结果的反射解析过程。
执行查询语句并返回单条数据
User user = userMapper.getUser(1);
System.out.println(user.getUsername());
理解SqlSession:
SqlSession意味着创建数据库会话,代表了一次与数据库的连接;
是mybatis对外提供数据访问的主要API,实际上SqlSeesion的功能都是基于Executor来实现的。
Executor是mybatis核心接口之一,定义了数据库操作最基本的方法。
Executor的三个重要组件
StatementHandler:它的作用是使用数据库的Statement或PrepareStatement执行操作,启承上启下作用;
ParameterHandler:对预编译的SQL语句进行参数设置
ResultSetHandler:对数据库返回的结果集(ResultSet)进行封装,返回用户指定的实体类型;
简单mybatis实现思路:
1、创建SqlSessionFactroy实例;
private static final String MAPPER_CONFIG_LOCATION = "mappers";
private static final String DB_CONFIG_FILE = "db.properties";
private final Configuration config = new Configuration();
public SqlSessionFactory() {
//加载数据库信息(db.properties)
loadDbInfo();
//加载mapper.xml信息
loadMappersInfo();
}
2、实例化过程中,加载配置文件创建configuration对象;
public class Configuration {
private String jdbcDriverClass;
private String jdbcUrl;
private String jdbcUsername;
private String jdbcPassword;
//存放多个mapper.xml文件的信息()
private Map<String,MapperdStatement> mapperdStatements = new HashMap<>();
}
public class MapperdStatement {
private String namespace; //命名空间
private String sourceId; //封装ID(命名空间+select的ID)
private String resultType; //select 返回类型
private String sql; //sql语句
}
3、通过factroy创建sqlSession;
public SqlSession openSession(){
return new DefaultSqlSession(config);
}
4、通过sqlSeesion获取mapper接口动态代理;
public <T> T getMapper(Class<T> type) {
//动态代理:InvocationHandler和Proxy
//参数:1、指定当前目标对象使用类加载器,获取加载器的方法是固定的
//2、目标对象实现的接口的类型,使用泛型方式确认类型
//3、事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入
//生成代理对象
MapperProxy mp = new MapperProxy(this);
return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, mp);
}
5、动态代理回调sqlSession中某查询方法;
public class MapperProxy implements InvocationHandler {
private SqlSession session;
public MapperProxy(SqlSession session) {
super();
this.session = session;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//三步翻译
//判断方法的返回类型是否是集合类的子类
//是就调用session的selectList的方法
if(Collection.class.isAssignableFrom(method.getReturnType())){
return session.selectList(method.getDeclaringClass().getName()+"."+method.getName(), args==null?null:args[0]);
}else{
return session.selectOne(method.getDeclaringClass().getName()+"."+method.getName(), args==null?null:args[0]);
}
}
}
6、SQLSession将查询方法转发给Executor;
private final Configuration config;
private Executor executor;
public <E> List<E> selectList(String statement, Object parameter) {
MapperdStatement ms = config.getMapperdStatements().get(statement);
return executor.query(ms, parameter);
}
7、Executor基于JDBC访问数据库获取数据;
@SuppressWarnings("unchecked")
@Override
public <E> List<E> query(MapperdStatement ms, Object parameter) {
List<E> ret = new ArrayList<E>();
try {
Class.forName(config.getJdbcDriverClass());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = DriverManager.getConnection(config.getJdbcUrl(),config.getJdbcUsername(),config.getJdbcPassword());
ps = conn.prepareStatement(ms.getSql());
ps.setString(1, parameter.toString());
//处理占位符
// parameterize(ps,parameter);
rs = ps.executeQuery();
User user = new User();
while(rs.next()){
user.setId(rs.getInt(1));
user.setUsername(rs.getString(2));
user.setPassword(rs.getString(3));
ret.add((E)user);
}
return ret;
//把结果集反射到对象上
// handlerResultSet(rs,ret,ms.getResultType());
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
try {
if(rs!=null){
rs.close();
}
if(ps!=null){
ps.close();
}
if(conn!=null){
conn.close();
}
} catch (Exception e1) {
e1.printStackTrace();
}
}
return null;
}
//mybatis源码
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 创建StatementHandler对象
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 完成Statement的创建和初始化,并使用parameterHandle对占位符进行处理
stmt = prepareStatement(handler, ms.getStatementLog());
// 调用StatementHandler.query方法,执行SQL语句,
//并通过ResultSetHandler完成结果集的映射
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//获取connection对象的动态代理,添加日志能力
Connection connection = getConnection(statementLog);
//通过不同的StatementHandler,利用connection创建(parame)Statement
stmt = handler.prepare(connection, transaction.getTimeout());
//调用parameterHandler处理占位符
handler.parameterize(stmt);
return stmt;
}
8、Executor通过反射将数据转换成POJO并返回给sqlSession;
//mybatis源码
//处理占位符的具体业务实现
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
//从boundSql中获取sql语句的占位符对应的参数信息
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
//遍历参数列表,把参数设置到preparedStatement中
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
//对于存储过程中的参数不处理
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;//绑定的实体
String propertyName = parameterMapping.getProperty();//参数的名字
if (boundSql.hasAdditionalParameter(propertyName)) { // 获取对应的实参值
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
//为statement中的占位符绑定参数
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
//查询结果集在这里处理,使用反射技术将查询结果映射到实体类中
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
//用于保存结果集对象
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
//statement可能返回多个结果集对象,这里先取出第一个结果集
ResultSetWrapper rsw = getFirstResultSet(stmt);
//获取结果集对应的resultMap
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
9、将数据返回给调用者
public <E> List<E> selectList(String statement, Object parameter) {
MapperdStatement ms = config.getMapperdStatements().get(statement);
return executor.query(ms, parameter);
}
mybatis的两种编程模型:
1、使用mapper接口编程,就可以访问数据库;
//--第一阶段--
//1、读取mybatis配置文件,创建SqlSessionFactroy
String resource = "mybatis/mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
SqlSessionFactory factroy = new SqlSessionFactoryBuilder().build(is);
is.close();
//--第二阶段--
//2、获取sqlSession
SqlSession sqlSession = factroy.openSession();
//3、获取对应mapper
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//--第三阶段--
//4、执行查询语句并返回单条数据
User user = userMapper.getUser(1);
System.out.println(user.getUsername());
2、使用sqlsession对外提供数据库的访问。
//1、读取mybatis配置文件,创建SqlSessionFactroy
String resource = "mybatis/mybatis-config.xml";
InputStream is = Resources.getResourceAsStream(resource);
SqlSessionFactory factroy = new SqlSessionFactoryBuilder().build(is);
is.close();
//2、获取sqlSession
SqlSession sqlSession = factroy.openSession();
User user = sqlSession.selectOne("com.mybatis.mapper.UserMapper.getUser", 1);
System.out.println(user.getUsername());
为什么mapper接口没有实现类,也可调用方法查询语句:
实质上是mybatis内部都是实现的sqlSession接口开发。
mapper接口开发 内部翻译 sqlSession接口开发
配置文件解读+动态代理增强
找到session中对应的方法执行
找到命名空间和方法名
传递参数
————————————————
版权声明:本文为CSDN博主「努力材质无能」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/yunhaiwuya/article/details/93454974