当前位置:网站首页>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("");
}
}
边栏推荐
猜你喜欢

【Unity3D】相机跟随

如何高效优雅地管理接口文档

推荐两款超高质量的壁纸软件

独立站卖家如何高效管理复杂的Facebook主页?

What are the design requirements for PCB layout and wiring?

1 invalid import format(s) Postman Collection Format v1 is no longer supported and can not be import

CORBA 架构体系指南(通用对象请求代理体系架构)

第2章 处理文件、摄像头和图形用户界面cameo应用

Record an emotet Trojan horse handling case

向上转型和向下转型
随机推荐
Applet graduation design based on wechat conference room reservation applet graduation design opening report function reference
Yixin Huachen: real estate enterprises want to grasp the opportunity of the times for digital transformation
【Unity3D】相机跟随
模块化操作
Common DOS commands
中金财富开户安全吗?开过中金财富的讲一下
内存泄露
Konva series tutorial 3: Customizing drawings
技术管理进阶——管理者如何做绩效沟通及把控风险
ONEFLOW source code parsing: automatic inference of operator signature
浦发银行软件测试面试真题(小编面试亲测)
BioVendor游离轻链(κ和λ)Elisa 试剂盒检测步骤
新工作第一天
Cann media data processing V2, jpegd interface introduction
Go 降序排序 取 Top N
Introduction to apifox
推荐两款超高质量的壁纸软件
Openfire用户以及群组关系移植
19.2 容器分类、array、vector容器精解
About Critical Values