增加下面的配置,就会把spring-mybatis中debug的日志也打印出来
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2194ede9] was not registered for synchronization because synchronization is not active
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@25ab6d03] will not be managed by Spring
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4a88fe6e] was not registered for synchronization because synchronization is not active
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@5aeb8427] will not be managed by Spring
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4a88fe6e]
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@df3739] was not registered for synchronization because synchronization is not active
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@5aeb8427] will not be managed by Spring
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@2194ede9]
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@df3739]
org.mybatis.spring.transaction.SpringManagedTransaction#openConnection
/**
* Gets a connection from Spring transaction manager and discovers if this {@code Transaction} should manage
* connection or let it to Spring.
* <p>
* It also reads autocommit setting because when using Spring Transaction MyBatis thinks that autocommit is always
* false and will always call commit/rollback so we need to no-op that calls.
*/
private void openConnection() throws SQLException {
this.connection = DataSourceUtils.getConnection(this.dataSource);
this.autoCommit = this.connection.getAutoCommit();
this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);
LOGGER.debug(() -> "JDBC Connection [" + this.connection + "] will"
+ (this.isConnectionTransactional ? " " : " not ") + "be managed by Spring");
}
org.mybatis.spring.SqlSessionUtils#getSqlSession(org.apache.ibatis.session.SqlSessionFactory, org.apache.ibatis.session.ExecutorType, org.springframework.dao.support.PersistenceExceptionTranslator)
/**
* Gets an SqlSession from Spring Transaction Manager or creates a new one if needed. Tries to get a SqlSession out of
* current transaction. If there is not any, it creates a new one. Then, it synchronizes the SqlSession with the
* transaction if Spring TX is active and <code>SpringManagedTransactionFactory</code> is configured as a transaction
* manager.
*
* @param sessionFactory
* a MyBatis {@code SqlSessionFactory} to create new sessions
* @param executorType
* The executor type of the SqlSession to create
* @param exceptionTranslator
* Optional. Translates SqlSession.commit() exceptions to Spring exceptions.
* @return an SqlSession managed by Spring Transaction Manager
* @throws TransientDataAccessResourceException
* if a transaction is active and the {@code SqlSessionFactory} is not using a
* {@code SpringManagedTransactionFactory}
* @see SpringManagedTransactionFactory
*/
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
LOGGER.debug(() -> "Creating a new SqlSession");
session = sessionFactory.openSession(executorType);
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
org.mybatis.spring.SqlSessionUtils#registerSessionHolder
/**
* Register session holder if synchronization is active (i.e. a Spring TX is active).
*
* Note: The DataSource used by the Environment should be synchronized with the transaction either through
* DataSourceTxMgr or another tx synchronization. Further assume that if an exception is thrown, whatever started the
* transaction will handle closing / rolling back the Connection associated with the SqlSession.
*
* @param sessionFactory
* sqlSessionFactory used for registration.
* @param executorType
* executorType used for registration.
* @param exceptionTranslator
* persistenceExceptionTranslator used for registration.
* @param session
* sqlSession used for registration.
*/
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
SqlSessionHolder holder;
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Environment environment = sessionFactory.getConfiguration().getEnvironment();
if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
LOGGER.debug(() -> "Registering transaction synchronization for SqlSession [" + session + "]");
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
TransactionSynchronizationManager
.registerSynchronization(new SqlSessionSynchronization(holder, sessionFactory));
holder.setSynchronizedWithTransaction(true);
holder.requested();
} else {
if (TransactionSynchronizationManager.getResource(environment.getDataSource()) == null) {
LOGGER.debug(() -> "SqlSession [" + session
+ "] was not registered for synchronization because DataSource is not transactional");
} else {
throw new TransientDataAccessResourceException(
"SqlSessionFactory must be using a SpringManagedTransactionFactory in order to use Spring transaction synchronization");
}
}
} else {
LOGGER.debug(() -> "SqlSession [" + session
+ "] was not registered for synchronization because synchronization is not active");
}
}
org.mybatis.spring.SqlSessionUtils#closeSqlSession
/**
* Checks if {@code SqlSession} passed as an argument is managed by Spring {@code TransactionSynchronizationManager}
* If it is not, it closes it, otherwise it just updates the reference counter and lets Spring call the close callback
* when the managed transaction ends
*
* @param session
* a target SqlSession
* @param sessionFactory
* a factory of SqlSession
*/
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
notNull(session, NO_SQL_SESSION_SPECIFIED);
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
if ((holder != null) && (holder.getSqlSession() == session)) {
LOGGER.debug(() -> "Releasing transactional SqlSession [" + session + "]");
holder.released();
} else {
LOGGER.debug(() -> "Closing non transactional SqlSession [" + session + "]");
session.close();
}
}
命名解析:为了减少输入量,MyBatis 对所有的命名配置元素(包括语句,结果映射,缓存等)使用了如下的命名解析规则。
- 完全限定名(比如“com.mypackage.MyMapper.selectAllThings”)将被直接查找并且找到即用。
- 短名称(比如“selectAllThings”)如果全局唯一也可以作为一个单独的引用。如果不唯一,有两个或两个以上的相同名称(比如“com.foo.selectAllThings ”和“com.bar.selectAllThings”),那么使用时就会收到错误报告说短名称是不唯一的,这种情况下就必须使用完全限定名。
对于像 BlogMapper 这样的映射器类(Mapper class)来说,还有另一招来处理。它们的映射的语句可以不需要用 XML 来做,取而代之的是可以使用 Java 注解。比如,上面的 XML 示例可被替换如下:
package org.mybatis.example;
public interface BlogMapper {
@Select("SELECT * FROM blog WHERE id = #{id}")
Blog selectBlog(int id);
}
对于简单语句来说,注解使代码显得更加简洁,然而 Java 注解对于稍微复杂的语句就会力不从心并且会显得更加混乱。因此,如果你需要做很复杂的事情,那么最好使用 XML 来映射语句。
选择何种方式以及映射语句的定义的一致性对你来说有多重要这些完全取决于你和你的团队。换句话说,永远不要拘泥于一种方式,你可以很轻松的在基于注解和 XML 的语句映射方式间自由移植和切换。
http://www.mybatis.org/mybatis-3/zh/getting-started.html
说明:Java-based Config。
不是通过 mybatis 的 SqlSessionFactoryBuilder 来创建 SqlSessionFactory ,而是通过 mybatis-spring 的 SqlSessionFactoryBean 来获取。
1、首先要有一个DataSource 。
需要注意,事务管理器也在这里注册。(mybatis-spring插件会自动调用该事务管理器)
@Bean(name = "transactionManager")
public DataSourceTransactionManager dataSourceTransactionManager() {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(this.dataSource());
return dataSourceTransactionManager;
}
2、然后,注册SqlSessionFactoryBean(或者SqlSessionFactory,二选一,内容一致)。如下:
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(this.dataSource());
// sqlSessionFactoryBean.setConfigLocation(new ClassPathResource("mybatis-config.xml")); // 这里可以通过mybatis-config.xml 来设置 typeAliasPackage和mapper。
// Resource[] mapperLocations = new Resource[] { new ClassPathResource("com.expert.dao") }; // 这个和@MapperScan冲突吗?这个设置有问题。
// sqlSessionFactoryBean.setMapperLocations(mapperLocations);//<mappers>
sqlSessionFactoryBean.setTypeAliasesPackage(PojoBasePackage);
// sqlSessionFactoryBean.setCache(cache);
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBean.getObject();
// It can be specified a Configuration instance directly without MyBatis XML configuration file.
sqlSessionFactory.getConfiguration().setMapUnderscoreToCamelCase(true);// 开启驼峰映射
sqlSessionFactory.getConfiguration().setCacheEnabled(true);
sqlSessionFactory.getConfiguration().setLazyLoadingEnabled(true);
sqlSessionFactory.getConfiguration().setAggressiveLazyLoading(false);
// Class<Object> logImpl = sqlSessionFactory.getConfiguration().getTypeAliasRegistry().resolveAlias("SLF4J");
sqlSessionFactory.getConfiguration().setLogImpl(Slf4jImpl.class);// logImpl
sqlSessionFactory.getConfiguration().setLogPrefix("###SPRING_BOOT###MYBATIS###");
sqlSessionFactory.getConfiguration().setDefaultExecutorType(ExecutorType.REUSE);
sqlSessionFactory.getConfiguration().setUseGeneratedKeys(true);
return sqlSessionFactory;
}
这里还设置了一堆参数。需要注意的是,
①设置了 TypeAliasesPackage 。
②设置了 Configuration 。
③mybatis-spring会自动创建 Configuration 对象,所以通过 sqlSessionFactory.getConfiguration() 即可获取并进行设置。
3、再注册一个 SqlSessionTemplate,这是 mybatis-spring 的核心。
@Bean
@Scope(BeanDefinition.SCOPE_PROTOTYPE) // 多例?
public SqlSessionTemplate sqlSessionTemplate() throws Exception {
return new SqlSessionTemplate(this.sqlSessionFactory());
}
4、设置mapper的位置,有两种方法。
①推荐这种,简单。
@Configuration
@MapperScan(basePackages = { "com.expert.dao" })
public class DruidDataSourceConfig{
// ...
}
②
@Bean
public MapperScannerConfigurer mapperScannerConfigurer() {
MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
mapperScannerConfigurer.setBasePackage(DaoBasePackage);
mapperScannerConfigurer.setSqlSessionFactoryBeanName("sqlSessionFactory");
return mapperScannerConfigurer;
}
至此,已可以成功运行。
但是,还有更简单的方法,那就是 MyBatis-Spring-Boot-Starter 。使用该Starter时,会自动查找DataSource,并自动创建SqlSessionFactoryBean 和 SqlSessionTemplate。所以,只需要设置mapper所在的位置和别名所在的包即可。
见 MyBatis-Spring-Boot 使用总结 。
@ConfigurationProperties(prefix = MybatisProperties.MYBATIS_PREFIX)
public class MybatisProperties {
public static final String MYBATIS_PREFIX = "mybatis";
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnBean(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter(DataSourceAutoConfiguration.class)
public class MybatisAutoConfiguration {
Caused by: java.lang.IllegalArgumentException: Mapped Statements collection already contains value for com.retry.business.repository.mapper.RetryTaskDOMapper.findAllFailByBizId. please check URL [jar:file:/app.jar!/BOOT-INF/classes!/mapper/retry/RetryTaskDOMapper.xml] and URL [jar:file:/app.jar!/BOOT-INF/classes!/mapper/retry/RetryTaskDOMapper.xml]
解决办法:删除RetryTaskDOMapper.xml文件中重复的id为findAllFailByBizId 一个sql就可以了
原因:
意思是这个sql绑定了一个ResultMap了,不能重复绑定。