当前位置:网站首页>Mybayis之核心主件分析
Mybayis之核心主件分析
2022-06-28 18:32:00 【且听风吟0220】
1、MybatisAutoConfiguration
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({
SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({
DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {
//dataSource 为HikariDataSource
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
...
return factory.getObject();
}
}
1.1、SqlSessionFactoryBean
public class SqlSessionFactoryBean
implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent>{
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
@Override
public void afterPropertiesSet() throws Exception {
...
this.sqlSessionFactory = buildSqlSessionFactory();
}
}
1.2、初始化DefaultSqlSessionFactory & Configuration
// 返回 DefaultSqlSessionFactory
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
final Configuration targetConfiguration;
XMLConfigBuilder xmlConfigBuilder = null;
...
xmlConfigBuilder.parse();
// Environment持有DataSource、SpringManagedTransactionFactory
targetConfiguration.setEnvironment(new Environment(this.environment,
this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
this.dataSource));
if (this.mapperLocations != null) {
for (Resource mapperLocation : this.mapperLocations) {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
xmlMapperBuilder.parse();
}
}
// targetConfiguration 持有Mybatis所有配置信息
// DefaultSqlSessionFactory 持有 targetConfiguration
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
Environment
//org.apache.ibatis.mapping.Environment
public Environment(String id, TransactionFactory transactionFactory, DataSource dataSource) {
...
this.transactionFactory = transactionFactory;//SpringManagedTransactionFactory
this.dataSource = dataSource;//HikariDataSource
}
1.3、SqlSessionTemplate
1.3.1、服务启动初始化SqlSessionTemplate
public class MybatisAutoConfiguration implements InitializingBean {
// sqlSessionFactory为DefaultSqlSessionFactory
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
}
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
// sqlSessionFactory = DefaultSqlSessionFactory
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
// 对DefaultSqlSession的代理
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] {
SqlSession.class }, new SqlSessionInterceptor());
}
1.3.2、SQL执行过程中初始化DefaultSqlSessionFactory
利用 DefaultSqlSessionFactory 创建 DefaultSqlSession。
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// #3.1:通过工厂类创建SqlSession。
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
// #3.2:调用 DefaultSqlSession#selectList
Object result = method.invoke(sqlSession, args);
return result;
}
}
2、MapperProxy
使用jdk动态代理执行dao层的目标方法。
public class MapperProxy<T> implements InvocationHandler, Serializable {
// sqlSession为 SqlSessionTemplate
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
...
final MapperMethod mapperMethod = cachedMapperMethod(method);
// sqlSession = SqlSessionTemplate。最终调用 SqlSessionTemplate#selectList
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method,
k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
}
public class SqlSessionTemplate implements SqlSession, DisposableBean{
public <E> List<E> selectList(String statement, Object parameter) {
// sqlSessionProxy是对DefaultSqlSession的代理。
// 调用 SqlSessionTemplate#invoke方法
return this.sqlSessionProxy.selectList(statement, parameter);
}
}
3、SqlSession
- 针对Springboot中,其子类包括SqlSessionTemplate、DefaultSqlSession。
- 持有增删改查等基本api。以及辅助API「提交、关闭会话」。
- 每个SQL请求都会创建一个SqlSession会话处理。
3.1、DefaultSqlSessionFactory
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
final Environment environment = configuration.getEnvironment();
//transactionFactory:SpringManagedTransactionFactory
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// tx = SpringManagedTransaction,并持有 DataSource
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// # 4.1
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
}
3.2、DefaultSqlSession
public class DefaultSqlSession implements SqlSession {
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
MappedStatement ms = configuration.getMappedStatement(statement);
// #5.1:CacheExecutor#query
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
}
}
4、Configuration
4.1、创建Executor
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
// transaction:SpringManagedTransaction
// this:Configuration
// #5.2
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
//默认为true
// #5.1:CachingExecutor 持有真正执行SQL功能的SimpleExecutor
executor = new CachingExecutor(executor);
}
// #9 【初始化 Executor 相关的拦截器插件】
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
5、Executor
5.1、CacheExecutor
// delegate:SimpleExecutor
public CachingExecutor(Executor delegate) {
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 二级缓存key
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
//delegate 表示SimpleExecutor。SimpleExecutor是真正执行SQL功能,其对应的方法为doQuery或者doUpdate...
//此处利用 模版 方式,调用的是抽象类BaseExecutor#query方法「BaseExecutor主要是处理一级缓存」
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
5.2、SimpleExecutor
public SimpleExecutor(Configuration configuration, Transaction transaction) {
// #5.3
super(configuration, transaction);
}
5.2.1、初始化Connection
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
// #8.1 RoutingStatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler,
boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
// RoutingStatementHandler#query
return handler.query(stmt, resultHandler);
}
//handler:RoutingStatementHandler
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
// #7 通过 SpringManagedTransaction 获取 连接对象Connection
Connection connection = getConnection(statementLog);
// 如果 存在StatementHandler的插件,则调用 handler 为代理对象,直接调用 9.3、Plugin#invoke 【最终调用 PageInterceptors#intercept】,其中以 PageInterceptors 为例。
// 执行 PageInterceptors#intercept 方法之后,最终执行到 #8.1 RoutingStatementHandler#prepare
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
5.3、BaseExecutor
//transaction:SpringManagedTransaction
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.configuration = configuration;
}
5.3.1、处理一级缓存localCache
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
...
List<E> list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
...
return list;
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//#5.2~doQuery
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
...
return list;
}
6、 MappedStatement
public final class MappedStatement{
private Configuration configuration;
//net.ss.mapper.CommentDAO.query || net.ss.mapper.CommentDAO.save等
private String id;
private StatementType statementType;
private ResultSetType resultSetType;
private SqlSource sqlSource;
private Cache cache;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
}
基于 groovy + 注解方式分析。启动过程中处理所有的接口dao层。
public class Configuration {
// type:表示dao层的接口,net.ss.mapper.CommentDAO
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
public <T> void addMapper(Class<T> type) {
...
knownMappers.put(type, new MapperProxyFactory<>(type));//用于dao层接口实现动态代理
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
...
}
}
public class MapperAnnotationBuilder{
public void parse() {
Method[] methods = type.getMethods();
for (Method method : methods) {
parseStatement(method);
}
}
void parseStatement(Method method) {
...
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
//初始化 MappedStatement
assistant.addMappedStatement(...)
}
}
public class MapperBuilderAssistant extends BaseBuilder {
public MappedStatement addMappedStatement(...){
...
MappedStatement statement = statementBuilder.build();
// 由Configuration属性「Map<String, MappedStatement> mappedStatements」持有所有的MappedStatement
configuration.addMappedStatement(statement);
}
}
6.1、SqlSource
BoundSql getBoundSql(Object parameterObject);
6.2、BoundSql
public class BoundSql {
private final String sql;
private final List<ParameterMapping> parameterMappings;
private final Object parameterObject;
private final Map<String, Object> additionalParameters;
private final MetaObject metaParameters;
public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object
parameterObject) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.parameterObject = parameterObject;
this.additionalParameters = new HashMap<>();
this.metaParameters = configuration.newMetaObject(additionalParameters);
}
}
7、Connection
public abstract class BaseExecutor implements Executor{
// 通过 SpringManagedTransaction 获取 连接Connection
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
}
public class SpringManagedTransaction implements Transaction {
private void openConnection() throws SQLException {
// dataSource:HikariDataSource
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
}
}
8、StatementHandler
8.1、RoutingStatementHandler
public class Configuration {
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object
parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject,
rowBounds, resultHandler, boundSql);
// #9:【初始化 StatementHandler的拦截器插件】
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
}
RoutingStatementHandler中代理的类都是继承至抽象类BaseStatementHandler的实现类。
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
}
}
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
// delegate:PreparedStatementHandler,根据模板模式,最终调用BaseStatementHandler#prepare
return delegate.prepare(connection, transactionTimeout);
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//delegate:PreparedStatementHandler
return delegate.query(statement, resultHandler);
}
}
public abstract class BaseStatementHandler implements StatementHandler {
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject,
RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) {
// issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
// 初始化 ParameterHandler
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
//初始化 ResultSetHandler
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler,
resultHandler, boundSql);
}
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 根据connection获取 HikariProxyPreparedStatement
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
}
}
}
8.2、ParameterHandler
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql{
// DefaultParameterHandler
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement,
parameterObject, boundSql);
// #9:【初始化 ParameterHandler的拦截器插件】
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
8.3、ResultSetHandler
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds,
ParameterHandler parameterHandler,ResultHandler resultHandler, BoundSql boundSql) {
// DefaultResultSetHandler
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
// #9:【初始化 ResultSetHandler的拦截器插件】
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
8.4、PreparedStatementHandler
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 最终执行原生jdbc对数据的操作
ps.execute();
//处理结果映射
return resultSetHandler.handleResultSets(ps);
}
9、拦截器拦截插件
9.1、InterceptorChain
public class InterceptorChain {
//用户自定义的所有拦截器插件
private final List<Interceptor> interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
}
9.2、PageInterceptors
以分页插件 PageInterceptors 为示例:
@Override
public Object plugin(Object target) {
return Plugin.wrap(target,this);
}
9.3、Plugin
public class Plugin implements InvocationHandler {
private final Object target;
private final Interceptor interceptor;
private final Map<Class<?>, Set<Method>> signatureMap;
private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
this.target = target;
this.interceptor = interceptor;
this.signatureMap = signatureMap;
}
// target:拦截器插件类型,interceptor:自定义拦截器【PageInterceptors】
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
//如果当前type实现了拦截器,则通过jdk接口代理生成当前 type 的代理类
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
// 执行完
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
// interceptor 为自定义的 【PageInterceptors】
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
// 具体解析 某个拦截器
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
// 获取 注解@Intercepts的内容
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
...
// 获取 注解@Intercepts中@Signature注解的 所有value属性
Signature[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
for (Signature sig : sigs) {
//通过 @Signature 注解中配置的 method & args 确定当前type 下的具体的方法
Set<Method> methods = signatureMap.computeIfAbsent(sig.type(), k -> new HashSet<>());
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
}
return signatureMap;
}
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<>();
while (type != null) {
// 获取当前 type 下所有的接口
for (Class<?> c : type.getInterfaces()) {
// 实现插件拦截器的intercept,并非是当前type
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class<?>[interfaces.size()]);
}
}
SqlSession & Executor区别



- CacheExecutor:处理二级缓存。
- 抽象类BaseExecutor:负责处理基本功能&一级缓存。query & update 方法。
- SimpleExecutor真正执行SQL功能。doQuery & doUpdate 方法。
StatementHandler继承关系

PageInterceptors
@Intercepts(@Signature(type = StatementHandler.class,method = "prepare",args = {
Connection.class,Integer.class
}))
public class PageInterceptors implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
if(invocation.getTarget() instanceof StatementHandler){
StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
BoundSql boundSql = statementHandler.getBoundSql();
Object parameterObject = boundSql.getParameterObject();
// 检测当前参数是否满足分页逻辑
Page page = null;
if(parameterObject instanceof Page){
page = (Page)parameterObject;
}else if(parameterObject instanceof Map){
page = (Page) ((Map)parameterObject).values().stream().filter(v -> v instanceof Page).findFirst().orElse(null);
}
if(page == null){
return invocation.proceed();
}
page.setTotal(selectCount(invocation));
String newSql = String.format("%s limit %s,%s",boundSql.getSql(),page.getOffset(),page.getSize());
SystemMetaObject.forObject(boundSql).setValue("sql",newSql);
}
// 执行完代理类加强的方法intercept之后,继续执行被代理类【StatementHandler接口的子类】的prepare方法
return invocation.proceed();
}
public int selectCount(Invocation invocation) throws SQLException {
StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
String sql = statementHandler.getBoundSql().getSql();
String countSQL = String.format("select count(1) from (%s) as _page",sql);
Connection connection = (Connection)invocation.getArgs()[0];
PreparedStatement preparedStatement = connection.prepareStatement(countSQL);
statementHandler.getParameterHandler().setParameters(preparedStatement);
ResultSet resultSet = preparedStatement.executeQuery();
int count = 0;
while (resultSet.next()){
count = resultSet.getInt(1);
}
resultSet.close();
preparedStatement.close();
return count;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target,this);
}
@Override
public void setProperties(Properties properties) {
System.out.println("");
}
}
边栏推荐
- id门禁卡复制到手机_怎么把手机变成门禁卡 手机NFC复制门禁卡图文教程
- 堆的概念和代码实现
- Learning notes: how to time 10ms for 51 single chip microcomputer (STC89C52)
- select/poll/epoll
- Lumiprobe 蛋白质标记研究方案
- Steam education to break the barriers between disciplines
- 如何高效优雅地管理接口文档
- February 15, 2022 learning summary
- Qt 中 QObjectCleanupHandler 使用总结
- Chapter 2 processing files, cameras and GUI Cameo applications
猜你喜欢

声网 VQA:将实时互动中未知的视频画质用户主观体验变可知

What are the design requirements for PCB layout and wiring?

NFT流动性协议的安全困局—NFT借贷协议XCarnival被黑事件分析

Analysis of response parsing process of SAP ui5 batch request

Easyexcel learning notes

ONEFLOW source code parsing: automatic inference of operator signature

亿信华辰:地产企业数字化转型想要把握时代机遇

An error is reported when ActiveMQ is started. The 1883 port occupation problem is solved

IDM certification process log embedding point description

Unity about oculus quest2 developing 002-ui interaction based on XR interaction Toolkit
随机推荐
向上转型和向下转型
闭包的理解
CANN媒体数据处理V2,JPEGD接口介绍
Easyexcel learning notes
CORBA 架构体系指南(通用对象请求代理体系架构)
Openfire 3.8.2集群配置
【Unity3D】相机跟随
Sword finger offer 11 Minimum number of rotation array
1 invalid import format(s) Postman Collection Format v1 is no longer supported and can not be import
A preliminary study of IO model
What are the design requirements for PCB layout and wiring?
How to manage interface documents efficiently and gracefully
Applet graduation project reservation based on wechat housekeeping service applet graduation project opening report function reference
OOM out of memory 内存溢出
Introduction to apifox
用户网络模型与QoE
leetcode 1647. Minimum Deletions to Make Character Frequencies Unique(所有字母频率不同的最小删除次数)
About Covariance and Correlation(协方差和相关)
从理论到实践增强STEAM和工程教育
抗兔Dylight 488丨Abbkine通用型免疫荧光(IF)工具箱