@

前言

前面几篇文章分析了Mybatis的核心原理,但模块较多,没有一一分析,更多的需要读者自己下来研究。不过Mybatis的插件扩展机制还是非常重要的,像PageHelper就是一个扩展插件,熟悉其扩展原理,才能更好的针对我们的业务作出更合适的扩展。另外,现在Mybatis都是和Spring/SpringBoot一起使用,那么Mybatis又是如何与它们进行整合的呢?一切答案尽在本文之中。

正文

插件扩展

1. Interceptor核心实现原理

熟悉Mybatis配置的都知道,在xml配置中我们可以配置如下节点:

  <plugins>
<plugin interceptor="org.apache.ibatis.builder.ExamplePlugin">
<property name="pluginProperty" value="100"/>
</plugin>
</plugins>

这个就是插件的配置,那么自然而然的这个节点就会在解析xml的时候进行解析,并将其添加到Configuration中。细心的读者应该还记得下面这段代码,在XMLConfigBuilderl类中:

  private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
//解析<properties>节点
propertiesElement(root.evalNode("properties"));
//解析<settings>节点
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
//解析<typeAliases>节点
typeAliasesElement(root.evalNode("typeAliases"));
//解析<plugins>节点
pluginElement(root.evalNode("plugins"));
//解析<objectFactory>节点
objectFactoryElement(root.evalNode("objectFactory"));
//解析<objectWrapperFactory>节点
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//解析<reflectorFactory>节点
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);//将settings填充到configuration
// read it after objectFactory and objectWrapperFactory issue #631
//解析<environments>节点
environmentsElement(root.evalNode("environments"));
//解析<databaseIdProvider>节点
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//解析<typeHandlers>节点
typeHandlerElement(root.evalNode("typeHandlers"));
//解析<mappers>节点
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

其中pluginElement就是解析插件节点的:

  private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
//遍历所有的插件配置
for (XNode child : parent.getChildren()) {
//获取插件的类名
String interceptor = child.getStringAttribute("interceptor");
//获取插件的配置
Properties properties = child.getChildrenAsProperties();
//实例化插件对象
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
//设置插件属性
interceptorInstance.setProperties(properties);
//将插件添加到configuration对象,底层使用list保存所有的插件并记录顺序
configuration.addInterceptor(interceptorInstance);
}
}
}

从上面可以看到,就是根据配置实例化为Interceptor对象,并添加到InterceptorChain中,该类的对象被Configuration持有。Interceptor包含三个方法:

  //执行拦截逻辑的方法
Object intercept(Invocation invocation) throws Throwable; //target是被拦截的对象,它的作用就是给被拦截的对象生成一个代理对象
Object plugin(Object target); //读取在plugin中设置的参数
void setProperties(Properties properties);

InterceptorChain只是保存了所有的Interceptor,并提供方法给客户端调用,使得所有的Interceptor生成代理对象

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;
} public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
} public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
} }

可以看到pluginAll就是循环去调用了Interceptorplugin方法,而该方法的实现一般是通过Plugin.wrap去生成代理对象:

  public static Object wrap(Object target, Interceptor interceptor) {
//解析Interceptor上@Intercepts注解得到的signature信息
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();//获取目标对象的类型
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);//获取目标对象实现的接口
if (interfaces.length > 0) {
//使用jdk的方式创建动态代理
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}

其中getSignatureMap就是将@Intercepts注解中的value值解析并缓存起来,该注解的值是@Signature类型的数组,而这个注解可以定义class类型方法参数,即拦截器的定位。而getAllInterfaces就是获取要被代理的接口,然后通过JDK动态代理创建代理对象,可以看到InvocationHandler就是Plugin类,所以直接看invoke方法,最终就是调用interceptor.intercept方法:

  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.intercept方法进行拦截处理
return interceptor.intercept(new Invocation(target, method, args));
}
//如果当前方法不需要被拦截,则调用对象自身的方法
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}

这里的插件实现思路是通用的,即这个interceptor我们可以用来扩展任何对象的任何方法,比如对Mapget进行拦截,可像下面这样实现:

  @Intercepts({
@Signature(type = Map.class, method = "get", args = {Object.class})})
public static class AlwaysMapPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
return "Always";
} @Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
} @Override
public void setProperties(Properties properties) {
}
}

然后在使用Map时先用插件对其包装,这样拿到的就是Map的代理对象。

    Map map = new HashMap();
map = (Map) new AlwaysMapPlugin().plugin(map);

2. Mybatis的拦截增强

因为我们可以对Mybatis扩展任意多个的插件,所以它使用InterceptorChain对象来保存所有的插件,这是责任链模式的实现。那么Mybatis到底会拦截哪些对象和哪些方法呢?回忆上篇文章我们就可以发现Mybatis只会对以下4个对象进行拦截:

  • Executor
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
......省略 //通过interceptorChain遍历所有的插件为executor增强,添加插件的功能
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
  • StatementHandler
  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//创建RoutingStatementHandler对象,实际由statmentType来指定真实的StatementHandler来实现
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
  • ParameterHandler
  public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
  • ResultSetHandler
  public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}

而具体要拦截哪些对象和哪些方法则是由@Intercepts和@Signature指定的。

以上就是Mybatis扩展插件的实现机制,读者可据此自行分析下PageHelper的实现原理。另外需要注意,我们在进行自定义插件开发时,尤其要谨慎。因为直接关系到操作数据库,如果对插件的实现原理不透彻,很有可能引发难以估量的后果。

Mybatis与Spring整合原理

前面的示例都是单独使用Mybatis,可以看到需要创建SqlSessionFactorySqlSession对象,然后通过SqlSession去创建Mapper接口的代理对象,所以在与Spring整合时,显而易见的,我们就需要考虑以下几点:

  • 什么时候创建以及怎么创建SqlSessionFactorySqlSession
  • 什么时候创建以及怎么创建代理对象?
  • 如何将Mybatis的代理对象注入到IOC容器中?
  • Mybatis怎么保证和Spring在同一个事务中并且使用的是同一个连接?

那么如何实现以上几点呢?下文基于mybatis-spring-1.3.3版本分析。

1. SqlSessionFactory的创建

熟悉Spring源码的(如果不熟悉,可以阅读我之前的Spring系列源码)都知道Spring最重要的那些扩展点:

  • BeanDefinitionRegistryPostProcessor:Bean实例化前调用
  • BeanFactoryPostProcessor:Bean实例化前调用
  • InitializingBean:Bean实例化后调用
  • FactoryBean:实现该接口代替Spring管理一些特殊的Bean

其它还有很多,以上列举出来的就是Mybatis集成Spring所用到的扩展点。首先我们需要实例化SqlSessionFactory,而实例化该对象在Mybatis里实际上就是去解析一大堆配置并封装到该对象中,所以我们不能简单的使用<bean>标签来配置,为此Mybatis实现了一个类SqlSessionFactoryBean(这个类我们在以前使用整合包时都会配置),之前XML中的配置都以属性的方式放入到了该类中:

	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="typeAliasesPackage" value="com.enjoylearning.mybatis.entity" />
<property name="mapperLocations" value="classpath:sqlmapper/*.xml" />
</bean>

进入这个类,我们可以看到它实现了InitializingBeanFactoryBean接口,实现第一个接口的作用就是在该类实例化后立即去执行配置解析的阶段:

  public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together"); this.sqlSessionFactory = buildSqlSessionFactory();
}

具体的解析就在buildSqlSessionFactory方法中,这个方法比较长,但不复杂,这里就不贴代码了。而实现第二接口的作用就在于Spring获取该类实例时实际上会通过getObject方法返回SqlSessionFactory的实例,通过这两个接口就完成了SqlSessionFactory的实例化。

2. 扫描Mapper并创建代理对象

在整合之后我们除了要配置SqlSessionFactoryBean外,还要配置一个类:

 	 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.enjoylearning.mybatis.mapper" />
</bean>

这个类的作用就是用来扫描Mapper接口的,并且这个类实现了BeanDefinitionRegistryPostProcessorInitializingBean,这里实现第二个接口的作用主要是校验有没有配置待扫描包的路径

  public void afterPropertiesSet() throws Exception {
notNull(this.basePackage, "Property 'basePackage' is required");
}

主要看到postProcessBeanDefinitionRegistry方法:

  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
} ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
scanner.setAddToConfig(this.addToConfig);
scanner.setAnnotationClass(this.annotationClass);
scanner.setMarkerInterface(this.markerInterface);
scanner.setSqlSessionFactory(this.sqlSessionFactory);
scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
scanner.setResourceLoader(this.applicationContext);
scanner.setBeanNameGenerator(this.nameGenerator);
scanner.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

这里创建了一个扫描类,而这个扫描类是继承自Spring的ClassPathBeanDefinitionScanner,也就是会将扫描到的类封装为BeanDefinition注册到IOC容器中去:

	public int scan(String... basePackages) {
int beanCountAtScanStart = this.registry.getBeanDefinitionCount(); doScan(basePackages); // Register annotation config processors, if necessary.
if (this.includeAnnotationConfig) {
AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
} return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
} public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) {
logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
processBeanDefinitions(beanDefinitions);
} return beanDefinitions;
} private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition(); if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
} // the mapper interface is the original class of the bean
// but, the actual class of the bean is MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
definition.setBeanClass(this.mapperFactoryBean.getClass()); definition.getPropertyValues().add("addToConfig", this.addToConfig); boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
} if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
} if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
}
}
}

你可能会好奇,在哪里生成的代理对象?只是将Mapper接口注入到IOC有什么用呢?其实关键代码就在definition.setBeanClass(this.mapperFactoryBean.getClass()),这句代码的作用就是将每一个Mapper接口都转为MapperFactoryBean类型。

为什么要这么转呢?进入这个类你会发现它也是实现了FactoryBean接口的,所以自然而然的又是利用它来创建代理实现类对象:

  public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}

3. 如何整合Spring事务

Mybatis作为一个ORM框架,它是有自己的数据源和事务控制的,而Spring同样也会配置这两个,那么怎么将它们整合到一起呢?而不是在Service类调用Mapper接口时就切换了数据源和连接,那样肯定是不行的。

在使用Mybatis时,我们可以在xml中配置TransactionFactory事务工厂类,不过一般都会使用默认的JdbcTransactionFactory,而当与Spring整合后,默认的事务工厂类改为了SpringManagedTransactionFactory。回到SqlSessionFactoryBean读取配置的方法,在该方法中有下面这样一段代码:

    if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
} configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));

上面默认创建了SpringManagedTransactionFactory,同时还将我们xml中ref属性引用的dataSource添加到了Configuration中,这个工厂会创建下面这个事务控制对象:

  public Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit) {
return new SpringManagedTransaction(dataSource);
}

而这个方法是在DefaultSqlSessionFactory获取SqlSession时会调用:

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

这就保证使用的是同一个数据源对象,但是怎么保证拿到的是同一个连接和事务呢?关键就在于SpringManagedTransaction获取连接是怎么实现的:

  public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
} private void openConnection() throws SQLException {
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource); if (LOGGER.isDebugEnabled()) {
LOGGER.debug(
"JDBC Connection ["
+ this.connection
+ "] will"
+ (this.isConnectionTransactional ? " " : " not ")
+ "be managed by Spring");
}
}

这里委托给了DataSourceUtils获取连接:

	public static Connection getConnection(DataSource dataSource) throws CannotGetJdbcConnectionException {
try {
return doGetConnection(dataSource);
}
catch (SQLException ex) {
throw new CannotGetJdbcConnectionException("Could not get JDBC Connection", ex);
}
} public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified"); ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
conHolder.setConnection(dataSource.getConnection());
}
return conHolder.getConnection();
}
// Else we either got no holder or an empty thread-bound holder here. logger.debug("Fetching JDBC Connection from DataSource");
Connection con = dataSource.getConnection(); if (TransactionSynchronizationManager.isSynchronizationActive()) {
logger.debug("Registering transaction synchronization for JDBC Connection");
// Use same Connection for further JDBC actions within the transaction.
// Thread-bound object will get removed by synchronization at transaction completion.
ConnectionHolder holderToUse = conHolder;
if (holderToUse == null) {
holderToUse = new ConnectionHolder(con);
}
else {
holderToUse.setConnection(con);
}
holderToUse.requested();
TransactionSynchronizationManager.registerSynchronization(
new ConnectionSynchronization(holderToUse, dataSource));
holderToUse.setSynchronizedWithTransaction(true);
if (holderToUse != conHolder) {
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
}
} return con;
}

看到ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource)这段代码相信熟悉Spring源码的已经知道了,这个我在分析Spring事务源码时也讲过,通过DataSource对象拿到当前线程绑定的ConnectionHolder,这个对象是在Spring开启事务的时候存进去的。至此,关于Spring和Mybatis的整合原理我们就个搞清楚了,至于和SpringBoot的整合,读者可自行分析。最后,我再分享一个小扩展知识。

4. FactoryBean的扩展知识

很多读者可能不知道这个接口有什么作用,其实很简单,当我们有某个类由Spring实例化比较复杂,想要自己控制它的实例化时,就可以实现该接口。而实现该接口的类首先会被实例化并放入一级缓存,而当我们依赖注入我们真正想要的类时(如Mapper接口的代理类),就会从一级缓存中拿到FactoryBean实现类的实例,并判断是否实现了FactoryBean接口,如果是就会调用getObject方法返回我们真正想要的实例。

那如果我们确实想要拿到的就是FactoryBean实现类的实例该怎么办呢?只需要在传入的beanName前面加上“&”符号即可。

总结

本篇分析了Mybatis如何扩展插件以及插件的实现原理,但如非必要,切忌扩展插件,如果一定要,那么一定要非常谨慎。另外还结合Spirng的扩展点分析了Mybatis和Spring的整合原理,解决了困在我心中已久的一些疑惑,相信那也是大多数读者的疑惑,好好领悟这部分内容非常有利于我们自己对Spring进行扩展。

Mybatis插件扩展以及与Spring整合原理的更多相关文章

  1. Mybatis学习(六)————— Spring整合mybatis

    一.Spring整合mybatis思路 非常简单,这里先回顾一下mybatis最基础的根基, mybatis,有两个配置文件 全局配置文件SqlMapConfig.xml(配置数据源,全局变量,加载映 ...

  2. MyBatis学习(二):与Spring整合(非注解方式配置MyBatis)

    搭建SpringMVC的-->传送门<-- 一.环境搭建: 目录结构: 引用的JAR包: 如果是Maven搭建的话,pom.xml的配置如下: <?xml version=" ...

  3. 手写Mybatis和Spring整合简单版示例窥探Spring的强大扩展能力

    Spring 扩展点 **本人博客网站 **IT小神 www.itxiaoshen.com 官网地址****:https://spring.io/projects/spring-framework T ...

  4. 深入源码理解Spring整合MyBatis原理

    写在前面 聊一聊MyBatis的核心概念.Spring相关的核心内容,主要结合源码理解Spring是如何整合MyBatis的.(结合右侧目录了解吧) MyBatis相关核心概念粗略回顾 SqlSess ...

  5. Mybatis框架(8)---Mybatis插件原理

    Mybatis插件原理 在实际开发过程中,我们经常使用的Mybaits插件就是分页插件了,通过分页插件我们可以在不用写count语句和limit的情况下就可以获取分页后的数据,给我们开发带来很大 的便 ...

  6. spring基础:什么是框架,框架优势,spring优势,耦合内聚,什么是Ioc,IOC配置,set注入,第三方资源配置,综合案例spring整合mybatis实现

    知识点梳理 课堂讲义 1)Spring简介 1.1)什么是框架 源自于建筑学,隶属土木工程,后发展到软件工程领域 软件工程中框架的特点: 经过验证 具有一定功能 半成品 1.2)框架的优势 提高开发效 ...

  7. Mybatis与Spring整合,使用了maven管理项目,作为初学者觉得不错,转载下来

    转载自:http://www.cnblogs.com/xdp-gacl/p/4271627.html 一.搭建开发环境 1.1.使用Maven创建Web项目 执行如下命令: mvn archetype ...

  8. Intellij IDEA +MAVEN+Jetty实现Spring整合Mybatis

    1 pom.xml(这里出现transaction错误,是版本的问题) <project xmlns="http://maven.apache.org/POM/4.0.0" ...

  9. Spring学习总结(五)——Spring整合MyBatis(Maven+MySQL)二

    接着上一篇博客<Spring整合MyBatis(Maven+MySQL)一>继续. Spring的开放性和扩张性在J2EE应用领域得到了充分的证明,与其他优秀框架无缝的集成是Spring最 ...

随机推荐

  1. [蓝桥杯]算法提高 GPA

    问题描述 输入A,B两人的学分获取情况,输出两人GPA之差. 输入格式 输入的第一行包含一个整数n表示A的课程数,以下n行每行Si,Ci分别表示第i个课程的学分与A的表现. GPA=Σ(Si*Ci) ...

  2. 除了FastJson,你也应该了解一下Jackson(二)

    概览 上一篇文章介绍了Jackson中的映射器ObjectMapper,以及如何使用它来实现Json与Java对象之间的序列化和反序列化,最后介绍了Jackson中一些序列化/反序列化的高级特性.而本 ...

  3. pytorch入门2.1构建回归模型初体验(模型构建)

    pytorch入门2.x构建回归模型系列: pytorch入门2.0构建回归模型初体验(数据生成) pytorch入门2.1构建回归模型初体验(模型构建) pytorch入门2.2构建回归模型初体验( ...

  4. 09.Django-信号

    目录 Django中的信号及其用法 Django中内置的signal 内置信号的使用 自定义信号 Django中的信号及其用法 Django中提供了"信号调度",用于在框架执行操作 ...

  5. sourcetree 安装破解注册方法

    1.下载sourcetree安装包 2.点击安装到下图步骤 3.在网盘中下载accounts.json  文件,( 链接:https://pan.baidu.com/s/1tJd_xCh-B-oOwd ...

  6. 使用Python解密仿射密码

    新学期有一门密码学课,课上老师布置了一道密码学题,题目如下: 解密由仿射密码加密的密文“DBUHU SPANO SMPUS STMIU SBAKN OSMPU SS” 想解密这个密文,首先必须要知道仿 ...

  7. AIO,BIO,NIO,IO复用,同步,异步,阻塞和非阻塞

    (1)什么是NIO(Non-blocked IO),AIO,BIO (2) 区别 (3)select 与 epoll,poll区别 1.什么是socket?什么是I/O操作? 什么是socket? 实 ...

  8. flex弹性布局及其属性

    CSS3 弹性盒子内容 弹性盒子由弹性容器(Flex container)和弹性子元素(Flex item)组成. 弹性容器通过设置 display 属性的值为 flex 或 inline-flex将 ...

  9. Linux工具之开发调试命令

    目录 gcc gdb vim pmap pstack strace readelf objdump ldd gcc 详见 gcc -E 只预处理 gcc -S 生成汇编代码 gcc -c 生成可重定向 ...

  10. JavaWeb网上图书商城完整项目--day02-5.ajax校验功能之服务器端三层实现

    regist.jsp页面中有异步请求服务器来对表单进行校验: l  校验登录名是否已注册过: l  校验Email是否已注册过: l  校验验证码是否正确. 这说明在UserServlet中需要提供相 ...