高校网站群建设方案,上海企业服务云官网,500套wordpress模板下载,最简单的cms网站怎么做SqlSession是Mybatis最重要的构建之一#xff0c;可以认为Mybatis一系列的配置目的是生成类似JDBC生成的Connection对象的statement对象#xff0c;这样才能与数据库开启“沟通”#xff0c;通过SqlSession可以实现增删改查#xff08;当然现在更加推荐是使用Mapper接口形式…SqlSession是Mybatis最重要的构建之一可以认为Mybatis一系列的配置目的是生成类似JDBC生成的Connection对象的statement对象这样才能与数据库开启“沟通”通过SqlSession可以实现增删改查当然现在更加推荐是使用Mapper接口形式
1 .sqlsession的创建
SqlSessionFactoryBuilder创建SqlSessionFactory openSession,sqlSession 执行增删改查 用了注解是通过org.mybatis.spring.SqlSessionFactoryBean该类创建sqlsession的而mapper里面的每一个方法称为statement。
public void deleteUserTest() throws IOException {// mybatis配置文件String resource SqlMapConfig.xml;// 得到配置文件流InputStream inputStream Resources.getResourceAsStream(resource);// 创建会话工厂传入mybatis的配置文件信息SqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder().build(inputStream);// 通过工厂得到SqlSessionSqlSession sqlSession sqlSessionFactory.openSession();// 传入id删除 用户sqlSession.delete(test.deleteUser, 39); //更新 sqlSession.update(test.updateUser, user); //插入 sqlSession.insert(test.insertUser, user); //查询 ListUser list sqlSession.selectOne(test.findUserByName, 小明);// 提交事务 增删改 需要commit查询无需commitsqlSession.commit();// 关闭会话sqlSession.close();}2.SqlSession原理
SqlSession提供select/insert/update/delete方法在旧版本中使用使用SqlSession接口的这些方法但是新版的Mybatis中就会建议使用Mapper接口的方法。
映射器其实就是一个动态代理对象进入到MapperMethod的execute方法就能简单找到SqlSession的删除、更新、查询、选择方法从底层实现来说通过动态代理技术让接口跑起来之后采用命令模式最后还是采用了SqlSession的接口方法getMapper()方法等到Mapper执行SQL查询也就是说Mapper接口方法的实现底层还是采用SqlSession接口方法实现的。
3.SqlSession重要的四个对象
1Execute调度执行StatementHandler、ParmmeterHandler、ResultHandler执行相应的SQL语句2StatementHandler使用数据库中StatementPrepareStatement执行操作即底层是封装好了的prepareStatement3ParammeterHandler处理SQL参数4ResultHandler结果集ResultSet封装处理返回。Execute执行器 execute接口有以下方法
public interface Executor {ResultHandler NO_RESULT_HANDLER null;int update(MappedStatement var1, Object var2) throws SQLException;E ListE query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, CacheKey var5, BoundSql var6) throws SQLException;E ListE query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;E CursorE queryCursor(MappedStatement var1, Object var2, RowBounds var3) throws SQLException;ListBatchResult flushStatements() throws SQLException;void commit(boolean var1) throws SQLException;void rollback(boolean var1) throws SQLException;CacheKey createCacheKey(MappedStatement var1, Object var2, RowBounds var3, BoundSql var4);boolean isCached(MappedStatement var1, CacheKey var2);void clearLocalCache();void deferLoad(MappedStatement var1, MetaObject var2, String var3, CacheKey var4, Class? var5);Transaction getTransaction();void close(boolean var1);boolean isClosed();void setExecutorWrapper(Executor var1);
}执行器起到至关重要的作用它是真正执行Java与数据库交互的东西参与了整个SQL查询执行过程中。
1主要有三种执行器简易执行器SIMPLE不配置就是默认执行器、REUSE是一种重用预处理语句、BATCH批量更新、批量专用处理器
2执行器作用Executor会先调用StatementHandler的prepare()方法预编译SQL语句同时设置一些基本的运行参数然后调用StatementHandler的parameterize()方法实际上是启用了ParameterHandler设置参数设置参数resultHandler再组装查询结果返回调用者完成一次查询完成预编译简单总结起来就是即先预编译SQL语句之后设置参数跟JDBC的prepareStatement过程类似最后如果有查询结果就会组装返回。
4.Mapper Mybatis官方手册建议通过mapper对象访问mybatis因为使用mapper看起来更优雅就像下面这样
session sqlSessionFactory.openSession();
UserDao userDao session.getMapper(UserDao.class);
UserDto user new UserDto();
user.setUsername(iMbatis);
user.setPassword(iMbatis);
userDao.insertUser(user);那么这个mapper到底是什么呢它是如何创建的呢它又是怎么与sqlsession等关联起来的呢下面为你一一解答。
1、创建
表面上看mapper是在sqlsession里创建的但实际创建它的地方是MapperRegistry
public T T getMapper(ClassT type, SqlSession sqlSession) {if (!knownMappers.contains(type))throw new BindingException(Type type isnot known to the MapperRegistry.);try {return MapperProxy.newMapperProxy(type, sqlSession);} catch (Exceptione) {throw new BindingException(Error getting mapper instance. Cause: e, e);}
}可以看到mapper是一个代理对象它实现的接口就是传入的type这就是为什么mapper对象可以通过接口直接访问。同时还可以看到创建mapper代理对象时传入了sqlsession对象这样就把sqlsession也关联起来了。我们进一步看看MapperProxy.newMapperProxy(type,sqlSession); 背后发生了什么事情
public static T T newMapperProxy(ClassT mapperInterface, SqlSession sqlSession) {ClassLoader classLoader mapperInterface.getClassLoader();Class?[] interfaces new Class[]{mapperInterface};MapperProxy proxy new MapperProxy(sqlSession);return (T) Proxy.newProxyInstance(classLoader,interfaces, proxy);
}看起来没什么特别的和其他代理类的创建一样我们重点关注一下MapperProxy的invoke方法 2、MapperProxy 的 invoke
我们知道对被代理对象的方法的访问都会落实到代理者的invoke上来MapperProxy的invoke如下
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{if (method.getDeclaringClass() Object.class) {return method.invoke(this, args);}final Class? declaringInterface findDeclaringInterface(proxy, method);final MapperMethod mapperMethod new MapperMethod(declaringInterface, method, sqlSession);final Object result mapperMethod.execute(args);if (result null method.getReturnType().isPrimitive() !method.getReturnType().equals(Void.TYPE)) {throw new BindingException(Mapper method method.getName() ( method.getDeclaringClass() ) attempted toreturn null from a method with a primitive return type ( method.getReturnType() ).);}return result;
}可以看到invoke把执行权转交给了MapperMethod我们来看看MapperMethod里又是怎么运作的
public Object execute(Object[] args) {Object result null;if(SqlCommandType.INSERT type) {Object param getParam(args);result sqlSession.insert(commandName, param);} else if(SqlCommandType.UPDATE type) {Object param getParam(args);result sqlSession.update(commandName, param);} else if(SqlCommandType.DELETE type) {Object param getParam(args);result sqlSession.delete(commandName, param);} else if(SqlCommandType.SELECT type) {if (returnsVoid resultHandlerIndex ! null) {executeWithResultHandler(args);} else if (returnsList) {result executeForList(args);} else if (returnsMap) {result executeForMap(args);} else {Object param getParam(args);result sqlSession.selectOne(commandName, param);}} else {throw new BindingException(Unknown execution method for: commandName);}return result;}可以看到MapperMethod就像是一个分发者他根据参数和返回值类型选择不同的sqlsession方法来执行。这样mapper对象与sqlsession就真正的关联起来了。
5.Executor 前面提到过sqlsession只是一个门面真正发挥作用的是executor对sqlsession方法的访问最终都会落到executor的相应方法上去。Executor分成两大类一类是CacheExecutor另一类是普通Executor。Executor的创建前面已经介绍了下面介绍下他们的功能
CacheExecutor
CacheExecutor有一个重要属性delegate它保存的是某类普通的Executor值在构照时传入。执行数据库update操作时它直接调用delegate的update方法执行query方法时先尝试从cache中取值取不到再调用delegate的查询方法并将查询结果存入cache中。代码如下
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {if (ms ! null) {Cache cache ms.getCache();if (cache ! null) {flushCacheIfRequired(ms);cache.getReadWriteLock().readLock().lock();try {if (ms.isUseCache() resultHandler null) {CacheKey key createCacheKey(ms, parameterObject, rowBounds);final List cachedList (List)cache.getObject(key);if (cachedList ! null) {return cachedList;} else {List list delegate.query(ms,parameterObject, rowBounds, resultHandler);tcm.putObject(cache,key, list);return list;}} else {return delegate.query(ms,parameterObject, rowBounds, resultHandler);}} finally {cache.getReadWriteLock().readLock().unlock();}}}return delegate.query(ms,parameterObject, rowBounds, resultHandler);
}普通 Executor
普通Executor有3类他们都继承于BaseExecutorBatchExecutor专门用于执行批量sql操作ReuseExecutor会重用statement执行sql操作SimpleExecutor只是简单执行sql没有什么特别的。下面以SimpleExecutor为例
public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {Statementstmt null;try {Configuration configuration ms.getConfiguration();StatementHandler handler configuration.newStatementHandler(this, ms,parameter, rowBounds,resultHandler);stmt prepareStatement(handler);return handler.query(stmt, resultHandler);} finally {closeStatement(stmt);}
}可以看出Executor本质上也是个甩手掌柜具体的事情原来是StatementHandler来完成的。
6.StatementHandler 当Executor将指挥棒交给StatementHandler后接下来的工作就是StatementHandler的事了。我们先看看StatementHandler是如何创建的。
创建
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement,Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) {StatementHandler statementHandler new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler);statementHandler (StatementHandler) interceptorChain.pluginAll(statementHandler);return statementHandler;
}可以看到每次创建的StatementHandler都是RoutingStatementHandler它只是一个分发者他一个属性delegate用于指定用哪种具体的StatementHandler。可选的StatementHandler有SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler三种。选用哪种在mapper配置文件的每个statement里指定默认的是PreparedStatementHandler。同时还要注意到StatementHandler是可以被拦截器拦截的和Executor一样被拦截器拦截后的对像是一个代理对象。由于mybatis没有实现数据库的物理分页众多物理分页的实现都是在这个地方使用拦截器实现的本文作者也实现了一个分页拦截器在后续的章节会分享给大家敬请期待。
初始化
StatementHandler创建后需要执行一些初始操作比如statement的开启和参数设置、对于PreparedStatement还需要执行参数的设置操作等。代码如下
private Statement prepareStatement(StatementHandler handler) throwsSQLException {Statement stmt;Connection connection transaction.getConnection();stmt handler.prepare(connection);handler.parameterize(stmt);return stmt;
}
statement的开启和参数设置没什么特别的地方handler.parameterize倒是可以看看是怎么回事。handler.parameterize通过调用ParameterHandler的setParameters完成参数的设置ParameterHandler随着StatementHandler的创建而创建默认的实现是DefaultParameterHandlerpublic ParameterHandler newParameterHandler(MappedStatement mappedStatement, ObjectparameterObject, BoundSql boundSql) {ParameterHandler parameterHandler new DefaultParameterHandler(mappedStatement,parameterObject,boundSql);parameterHandler (ParameterHandler) interceptorChain.pluginAll(parameterHandler);return parameterHandler;
}同Executor和StatementHandler一样ParameterHandler也是可以被拦截的。
参数设置
DefaultParameterHandler里设置参数的代码如下
public void setParameters(PreparedStatement ps) throws SQLException {ErrorContext.instance().activity(settingparameters).object(mappedStatement.getParameterMap().getId());ListParameterMapping parameterMappings boundSql.getParameterMappings();if(parameterMappings ! null) {MetaObject metaObject parameterObject null ? null : configuration.newMetaObject(parameterObject);for (int i 0; i parameterMappings.size(); i) {ParameterMapping parameterMapping parameterMappings.get(i);if(parameterMapping.getMode() ! ParameterMode.OUT) {Object value;String propertyName parameterMapping.getProperty();PropertyTokenizer prop new PropertyTokenizer(propertyName);if (parameterObject null) {value null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())){value parameterObject;} else if (boundSql.hasAdditionalParameter(propertyName)){value boundSql.getAdditionalParameter(propertyName);} else if(propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX) boundSql.hasAdditionalParameter(prop.getName())){value boundSql.getAdditionalParameter(prop.getName());if (value ! null) {value configuration.newMetaObject(value).getValue(propertyName.substring(prop.getName().length()));}} else {value metaObject null ? null :metaObject.getValue(propertyName);}TypeHandler typeHandler parameterMapping.getTypeHandler();if (typeHandler null) {throw new ExecutorException(Therewas no TypeHandler found for parameter propertyName of statement mappedStatement.getId());}typeHandler.setParameter(ps, i 1, value,parameterMapping.getJdbcType());}}}
}这里面最重要的一句其实就是最后一句代码它的作用是用合适的TypeHandler完成参数的设置。那么什么是合适的TypeHandler呢它又是如何决断出来的呢BaseStatementHandler的构造方法里有这么一句
this.boundSql mappedStatement.getBoundSql(parameterObject);
它触发了sql 的解析在解析sql的过程中TypeHandler也被决断出来了决断的原则就是根据参数的类型和参数对应的JDBC类型决定使用哪个TypeHandler。比如参数类型是String的话就用StringTypeHandler参数类型是整数的话就用IntegerTypeHandler等。
参数设置完毕后执行数据库操作update或query。如果是query最后还有个查询结果的处理过程。
7.ResultSetHandler
结果处理
结果处理使用ResultSetHandler来完成默认的ResultSetHandler是FastResultSetHandler它在创建StatementHandler时一起创建代码如下
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatementmappedStatement,
RowBoundsrowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSqlboundSql) {ResultSetHandler resultSetHandler mappedStatement.hasNestedResultMaps() ? newNestedResultSetHandler(executor, mappedStatement, parameterHandler,resultHandler, boundSql, rowBounds): new FastResultSetHandler(executor,mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);resultSetHandler (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);return resultSetHandler;
}可以看出ResultSetHandler也是可以被拦截的可以编写自己的拦截器改变ResultSetHandler的默认行为。
// ResultSetHandler内部一条记录一条记录的处理在处理每条记录的每一列时会调用TypeHandler转换结果如下
protected boolean applyAutomaticMappings(ResultSet rs, ListString unmappedColumnNames,MetaObject metaObject) throws SQLException {boolean foundValues false;for (StringcolumnName : unmappedColumnNames) {final Stringproperty metaObject.findProperty(columnName);if (property! null) {final ClasspropertyType metaObject.getSetterType(property);if (typeHandlerRegistry.hasTypeHandler(propertyType)) {final TypeHandler typeHandler typeHandlerRegistry.getTypeHandler(propertyType);final Object value typeHandler.getResult(rs,columnName);if (value ! null) {metaObject.setValue(property, value);foundValues true;}}}}return foundValues;
}从代码里可以看到决断TypeHandler使用的是结果参数的属性类型。因此我们在定义作为结果的对象的属性时一定要考虑与数据库字段类型的兼容性。