mybatis使用入门、springboot整合,以及整合后的事务管理原理
mybatis是一款支持自定义SQL、存储过程和高级映射的持久化框架。通过封装几乎消除了使用者编写JDBC、手动设置参数和检索结果的代码,其底层实现通过XML配置文件、Java注解的方式来配置,将Mapper接口和POJO类映射到数据库
第一步:配置mybatis的运行环境
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="db.properties" />
<settings>
<setting name="logImpl" value="LOG4J"/>
<setting name="mapUnderscoreToCamelCase" value="true" />
</settings>
<!--包别名,便于在mapper xml文件中简写resultType-->
<typeAliases>
<package name="com.luhc.mybatis.simple.model" />
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="UNPOOLED">
<property name="url" value="${db.connectionURL}"/>
<property name="driver" value="${db.driverClass}"/>
<property name="username" value="${db.userName}"/>
<property name="password" value="${db.password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<!-- 配置Mapper接口 -->
<mapper resource="com/luhc/mybatis/simple/mapper/UserMapper.xml"/>
</mappers>
</configuration>
第二步:根据配置文件来创建SqlSessionFactoryBuilder、创建SqlsessionFactory、SqlSession、从SqlSession中获取Mapper接口代理类对象
@Test
public void testXMLConfig() throws IOException {
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.findAll();
Assert.assertFalse(users.isEmpty());
User user = userMapper.findByKey(1L);
Assert.assertNotNull(user);
}
@Test
public void testCodeConfig() throws IOException {
// 从properties文件中加载配置
Properties properties = new Properties();
properties.load(Resources.getResourceAsStream("db.properties"));
Properties configProperties = new Properties();
String connectUrl = properties.getProperty("db.connectionURL");
String driverClass = properties.getProperty("db.driverClass");
String userName = properties.getProperty("db.userName");
String password = properties.getProperty("db.password");
configProperties.setProperty("url", connectUrl);
configProperties.setProperty("driver", driverClass);
configProperties.setProperty("username", userName);
configProperties.setProperty("password", password);
// 配置数据源
UnpooledDataSourceFactory unpooledDataSourceFactory = new UnpooledDataSourceFactory();
unpooledDataSourceFactory.setProperties(configProperties);
DataSource dataSource = unpooledDataSourceFactory.getDataSource();
// 配置事务工厂
TransactionFactory transactionFactory = new JdbcTransactionFactory();
// 配置Enviroment对象
Environment environment = new Environment("development", transactionFactory, dataSource);
// 配置Configuration对象
Configuration configuration = new Configuration(environment);
// 配置mapper
configuration.addMapper(UserMapper.class);
// 创建SqlSessionFactory实例,用于获取SqlSession
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
// 创建SqlSession实例,用于获取Mapper接口的代理实例
SqlSession sqlSession = sqlSessionFactory.openSession();
// 执行Mapper接口的方法,进行实际的查询
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> users = userMapper.findAll();
Assert.assertFalse(users.isEmpty());
User user = userMapper.findByKey(1L);
Assert.assertNotNull(user);
}
定义XML文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.luhc.mybatis.simple.mapper.UserMapper">
<!--id对应接口中的方法名; resultType为映射的POJO类-->
<select id="findAll" resultType="com.luhc.mybatis.simple.model.User">
select * from user
</select>
<select id="findByKey" resultType="com.luhc.mybatis.simple.model.User">
select * from user where id = #{id}
</select>
</mapper>
定义接口:类名与XML中的namespace一致,方法名与XML中的id一致
public interface UserMapper {
List<User> findAll();
User findByKey(Long id);
}
public interface UserMapper {
@Select("select * from user")
List<User> findAll();
@Select("select * from user where id = #{id}")
User findByKey(Long id);
}
environment
实现多数据源,通过在配置中定义多个environment实例,在创建SqlSessionFactory的时候,传入指定的environment来实现多数据源并存的功能
类型转换
用于实现Jdbc数据类型Java类型之间的相互转换,通过实现接口TypeHandler来实现,mybatis提供了抽象类BaseTypeHandler来方便我们实现该接口。可参考文档 typeHandler
插件
用于拦截mybatis底层代码执行的机制,可参考文档 plugin 。 PageHelper
官方文档
mybatis-spring是一个使mybatis和spring无缝集成的库,由mybatis社区开发。主要作用:
在spring中配置DataSource
@Configuration
@PropertySource({"classpath:/db.properties"})
public class DataSourceConfig {
@Value("${db.connectionURL}")
private String jdbcUrl;
@Value("${db.driverClass}")
private String driverClass;
@Value("${db.userName}")
private String userName;
@Value("${db.password}")
private String password;
@Bean
public DataSource masterDataSource() {
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setUrl(jdbcUrl);
druidDataSource.setDriverClassName(driverClass);
druidDataSource.setUsername(userName);
druidDataSource.setPassword(password);
return druidDataSource;
}
}
定义对应的Mapper接口和POJO模型类
@Mapper
public interface CountryMapper {
@Delete({
"delete from country",
"where id = #{id,jdbcType=INTEGER}"
})
int deleteByPrimaryKey(Integer id);
}
@Data
public class Country {
// ...
}
在spring中配置SqlSessionFactory
@Configuration
@MapperScan("com.luhc.mapper") // 扫描
public class MybatisConfig {
@Bean
@Autowired
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean.getObject();
}
}
在直接使用mybatis的场景中,SqlSessionFactory通过SqlSessionFactoryBuilder来构建,在mybatis-spring集成环境中通过SqlSessionFactoryBean来构建,这是一个实现了spring接口FactoryBean、InitializingBean的类。在应用启动时会创建一个SqlSessionFactory的实例,并以sqlSessionFactory保存在spring IOC容器中。
解开这个问题需要回到SqlSessionFactoryBean在应用启动时创建SqlSessionFactory的过程
可以看出,经过这一个流程后,返回的SqlSessionFactory实现类为DefaultSqlSessionFactory,可以通过它获取其内部的SpringManagedTransactionFactory。
我们再来看看在DefaultSqlSessionFactory中是如何创建SqlSession的。
public class DefaultSqlSessionFactory implements SqlSessionFactory {
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
// 调用的是SpringManagedTransactionFactory#newTransaction方法
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
// 获取到的SqlSession
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();
}
}
}
通过DefaultSqlSessionFactory创建的SqlSession为DefaultSqlSession,其内部包含了Executor实例(内部包含了上一步中TransactionFactory创建的Transaction对象),追踪到DefaultSqlSession内部
public class DefaultSqlSession implements SqlSession {
@Override
public void commit(boolean force) {
try {
// 委托给Executor实例来进行实际的提交
executor.commit(isCommitOrRollbackRequired(force));
dirty = false;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
}
追踪到Executor内部、其实现类有SimpleExecutor(一般使用这个)、BatchExecutor(批量处理)、ReuseExecutor(?)
public abstract class BaseExecutor implements Executor {
@Override
public void commit(boolean required) throws SQLException {
if (closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
}
clearLocalCache();
flushStatements();
if (required) {
// 委托给Transaction实例处理
transaction.commit();
}
}
}
到了这一步,对应的Transaction实例为SpringManagedTransaction
public class SpringManagedTransaction implements Transaction {
@Override
public Connection getConnection() throws SQLException {
if (this.connection == null) {
openConnection();
}
return this.connection;
}
private void openConnection() throws SQLException {
// 用Spring的DataSourceUtils工具类获取链接
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");
}
}
@Override
public void commit() throws SQLException {
if (this.connection != null && !this.isConnectionTransactional && !this.autoCommit) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Committing JDBC Connection [" + this.connection + "]");
}
this.connection.commit();
}
}
}
SqlSessionTemplate利用动态代理生成SqlSession接口的代理对象,通过SqlSessionInterceptor拦截了方法执行,在获取SqlSession的时候委托给Spring的TransactionSynchronizationManager#getResource方法来获取(底层为ThreadLocal原理实现)
提供了便利的实现方式,让直接使用SqlSession的数据访问层可以使用SqlSessionTemplate来保证线程安全和委托spring事务管理
第一种,手动注册
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
sqlSessionFactory.setDataSource(dataSource());
return (SqlSessionFactory) sqlSessionFactory.getObject();
}
@Bean
public UserMapper userMapper() throws Exception {
SqlSessionTemplate sessionTemplate = new SqlSessionTemplate(sqlSessionFactory());
return sessionTemplate.getMapper(UserMapper.class);
}
第二种,自动扫描
使用@MapperScan注解,查看注解源码,发现可以指定要使用的SqlSessionFactory bean的名称
@Configuration
@MapperScan("com.luhc.mapper")
public class MybatisConfig {
@Bean
@Autowired
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean.getObject();
}
@Bean
@Autowired
public PlatformTransactionManager dataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
注册一个MapperScannerConfigurer实例
MapperScannerConfigurer实现了接口BeanDefinitionRegistryPostProcessor,在实例化时,spring框架会回调方法MapperScannerConfigurer#postProcessBeanDefinitionRegistry,进行mapper接口的扫描
mybatis-spring-boot-starter为了快速在springboot中集成mybatis,使用这个库,我们将消除模板式的xml配置代码,更少的XML配置。
直接引入依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
定义mapper接口
@Mapper
public interface CityMapper {
@Select("SELECT * FROM CITY WHERE state = #{state}")
City findByState(@Param("state") String state);
}
springboot启动类
@SpringBootApplication
public class SampleMybatisApplication implements CommandLineRunner {
private final CityMapper cityMapper;
public SampleMybatisApplication(CityMapper cityMapper) {
this.cityMapper = cityMapper;
}
public static void main(String[] args) {
SpringApplication.run(SampleMybatisApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
System.out.println(this.cityMapper.findByState("CA"));
}
}
引入依赖后,应用启动后将自动将DataSource实例注入并创建SqlSessionFactory、自动创建SqlSessionTemplate实例、自动扫描mapper接口并为其关联SqlSessionTemplate实例(保证SqlSession线程安全)。其内部默认会扫描到@Mapper注解标记的接口,如果需要更改这个机制,需要使用@MapperScan注解来配置
通过简单的XML配置,可以根据数据库表生成POJO映射类、XML mapper文件、Java mapper接口。而且对XML mapper文件不会覆盖掉自定义过的内容,只是写入自动生成有变动的部分,对于Java mapper接口文件则会直接覆盖。
这里提供一个在maven运行插件的方式,包含了常用配置的配置样例, 更多的配置详情请自行到官网挖掘
maven插件配置
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.7</version>
<configuration>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
<executions>
<execution>
<id>Generate MyBatis Artifacts</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!--生成lombok形式的java bean-->
<dependency>
<groupId>com.softwareloop</groupId>
<artifactId>mybatis-generator-lombok-plugin</artifactId>
<version>1.0</version>
</dependency>
</dependencies>
</plugin>
generatorConfig.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--指定外部.properties配置文件-->
<properties resource="db.properties" />
<!--defaultModelType=flat,设置一个表只生成一个java实体类-->
<context id="MySQL" targetRuntime="MyBatis3" defaultModelType="flat">
<!--定义分隔符-->
<property name="autoDelimitKeywordss" value="true" />
<property name="beginningDelimiter" value="`" />
<property name="endingDelimiter" value="`" />
<property name="javaFileEncoding" value="UTF-8" />
<!--生成lombok形式的java bean-->
<plugin type="com.softwareloop.mybatis.generator.plugins.LombokPlugin" />
<!--注释管理器-->
<commentGenerator>
<!--阻止生成注释-->
<!--<property name="suppressAllComments" value="true" />-->
<!--阻止生成的注释信息包含时间戳-->
<property name="suppressDate" value="true" />
<!--注释带上数据库表的注释信息-->
<property name="addRemarkComments" value="true" />
</commentGenerator>
<!--jdbc连接器-->
<jdbcConnection connectionURL="${db.connectionURL}"
driverClass="${db.driverClass}"
userId="${db.userName}"
password="${db.password}">
</jdbcConnection>
<!--jdbc类型与java类型转换器-->
<javaTypeResolver >
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<javaModelGenerator targetPackage="com.luhc.mybatis.simple.model" targetProject="src/main/java">
<!--不根据database划分不同的包-->
<property name="enableSubPackages" value="false" />
<!--从数据库查询的值不必去除空格-->
<property name="trimStrings" value="false" />
</javaModelGenerator>
<sqlMapGenerator targetPackage="com.luhc.mybatis.simple.mapper" targetProject="src/main/resources">
<!--不根据database划分不同的包-->
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<javaClientGenerator type="ANNOTATEDMAPPER" targetPackage="com.luhc.mybatis.simple.mapper" targetProject="src/main/java">
<!--不根据database划分不同的包-->
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<table schema="mybatis" tableName="country" >
<!--参考配置说明http://www.mybatis.org/generator/configreference/generatedKey.html-->
<generatedKey column="id" sqlStatement="SELECT LAST_INSERT_ID()" type="post" identity="true"/>
</table>
<!-- 生成多张表 -->
<table schema="mybatis" tableName="user">
<!--参考配置说明http://www.mybatis.org/generator/configreference/generatedKey.html-->
<generatedKey column="id" sqlStatement="SELECT LAST_INSERT_ID()" type="post" identity="true"/>
<!-- 指定该字段对应的Java类型 -->
<columnOverride column="sex" property="sex" javaType="com.luhc.mybatis.simple.model.SexEnum" />
</table>
</context>
</generatorConfiguration>
预定义了基本的单表CRUD操作方法,减少了手动编写CRUD的工作。
引入依赖
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.5</version>
</dependency>
maven插件配置,mybatis generator代码生成器
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>1.3.6</version>
<configuration>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
<executions>
<execution>
<id>Generate MyBatis Artifacts</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.6</version>
</dependency>
<!--生成lombok形式的java bean-->
<dependency>
<groupId>com.softwareloop</groupId>
<artifactId>mybatis-generator-lombok-plugin</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper</artifactId>
<version>4.1.5</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
generatorConfig配置
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<!--defaultModelType=flat,设置一个表只生成一个java实体类-->
<context id="MySQL" targetRuntime="MyBatis3Simple" defaultModelType="flat">
<!--定义分隔符-->
<property name="autoDelimitKeywordss" value="true" />
<property name="beginningDelimiter" value="`" />
<property name="endingDelimiter" value="`" />
<property name="javaFileEncoding" value="UTF-8" />
<!--生成lombok形式的java bean。与通用Mapper插件冲突-->
<!--<plugin type="com.softwareloop.mybatis.generator.plugins.LombokPlugin" />-->
<!--通用mapper插件-->
<plugin type="tk.mybatis.mapper.generator.MapperPlugin">
<!--生成的Mapper会继承这里指定的类-->
<property name="mappers" value="tk.mybatis.mapper.common.Mapper"/>
<property name="caseSensitive" value="true"/>
<property name="forceAnnotation" value="true"/>
<property name="beginningDelimiter" value="`"/>
<property name="endingDelimiter" value="`"/>
<property name="needsData" value="true" />
</plugin>
<!--注释管理器-->
<commentGenerator>
<!--阻止生成注释-->
<!--<property name="suppressAllComments" value="true" />-->
<!--阻止生成的注释信息包含时间戳-->
<property name="suppressDate" value="true" />
<!--注释带上数据库表的注释信息-->
<property name="addRemarkComments" value="true" />
</commentGenerator>
<!--jdbc连接器-->
<jdbcConnection connectionURL="jdbc:mysql://111.230.14.116:3306/mybatis?useUnicode=true&characterEncoding=utf8&useSSL=false"
driverClass="com.mysql.cj.jdbc.Driver"
userId="用户"
password="密码">
</jdbcConnection>
<!--jdbc类型与java类型转换器-->
<javaTypeResolver >
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<javaModelGenerator targetPackage="com.treeinsea.glimmer.model" targetProject="src/main/java">
<!--不根据database划分不同的包-->
<property name="enableSubPackages" value="false" />
<!--从数据库查询的值不必去除空格-->
<property name="trimStrings" value="false" />
</javaModelGenerator>
<sqlMapGenerator targetPackage="com.treeinsea.glimmer.mapper" targetProject="src/main/resources">
<!--不根据database划分不同的包-->
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<javaClientGenerator type="XMLMAPPER" targetPackage="com.treeinsea.glimmer.mapper" targetProject="src/main/java">
<!--不根据database划分不同的包-->
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!--catalog指定表所在的数据库,避免了同名表的问题-->
<table tableName="country" catalog="mybatis" >
<property name="ignoreQualifiersAtRuntime" value="true" />
<!--参考配置说明http://www.mybatis.org/generator/configreference/generatedKey.html-->
<generatedKey column="id" sqlStatement="SELECT LAST_INSERT_ID()" type="post" identity="true"/>
</table>
<table tableName="user" catalog="mybatis" >
<property name="ignoreQualifiersAtRuntime" value="true" />
<!--参考配置说明http://www.mybatis.org/generator/configreference/generatedKey.html-->
<generatedKey column="id" sqlStatement="SELECT LAST_INSERT_ID()" type="post" identity="true"/>
</table>
</context>
</generatorConfiguration>
application.yaml指定扫描的基接口
mapper:
mappers:
- tk.mybatis.mapper.common.Mapper
not-empty: true