您的位置:首页 > 编程语言 > Java开发

Spring Boot 数据访问集成 MyBatis 与事物配置

2017-09-02 14:58 906 查看
对于软件系统而言,持久化数据到数据库是至关重要的一部分。在 Java 领域,有很多的实现了数据持久化层的工具和框架(ORM)。ORM 框架的本质是简化编程中操作数据库的繁琐性,比如可以根据对象生成 SQL 的 Hibernate ,后面 Hibernate 也实现了JPA 的规范,使用 JPA 的方式只需要几行代码即可实现对数据的访问和操作;MyBatis 的前身是 IBATIS 是一个简化和实现了 Java 数据持久化层的开源框架,相对的不同之处可以灵活调试 SQL , MyBatis 流行的主要原因在于它的简单性和易使用性。

集成 MyBatis

在之前配置 Spring MVC 集成 MyBatis 需要配置文件(SQL写在XML中)、实体类、dao层映射关联等繁琐的配置,后面又开发了 generator ,可以根据表自动生产实体类、配置文件和数据层代码,一定程度上简单了一些编码工作,当然也可以使用注解的方式来配置,简化到现在 Spring Boot 中集成 spring-boot-starter 就可以通过注解的方式直接写 SQL 语句,原则就是约定到大于配置,Spring Boot 要做的就是简化一切。

maven 配置

<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.2</version>
</dependency>
<!—jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.1</version>
</dependency>

application.properties 配置

#mysql 配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://172.0.0.1:3306/test?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
spring.datasource.username = root
spring.datasource.password = 123456

spring.datasource.druid.initial-size=10
spring.datasource.druid.max-active=30
spring.datasource.druid.min-idle=10
spring.datasource.druid.max-wait=10
#spring.datasource.druid.pool-prepared-statements=
#spring.datasource.druid.max-pool-prepared-statement-per-connection-size=
#spring.datasource.druid.max-open-prepared-statements= #和上面的等价
#spring.datasource.druid.validation-query=
#spring.datasource.druid.validation-query-timeout=
#spring.datasource.druid.test-on-borrow=
#spring.datasource.druid.test-on-return=
#spring.datasource.druid.test-while-idle=
#spring.datasource.druid.time-between-eviction-runs-millis=
#spring.datasource.druid.min-evictable-idle-time-millis=
#spring.datasource.druid.max-evictable-idle-time-millis=
#spring.datasource.druid.filters= #配置多个英文逗号分隔

上述配置 Spring Boot 会自动加载 spring.datasource.* 相关配置,从而进行 DataSource 对象的配置初始化数据库连接池(这里使用了阿里的 Druid) 。

基于注解的方式的 SQL 配置

实体类
public class Users {
private Long id;
private String openid;
private String username;
private Long sex;
private java.sql.Date birthday;
private String hometown;
private String profession;
private Long single;
private String constellation;
private String mobile;
private String signture;
private java.sql.Timestamp create_time;

public Long getId() {
return id;
}

public void setId(Long id) {
this.id = id;
}

public String getOpenid() {
return openid;
}

public void setOpenid(String openid) {
this.openid = openid;
}

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}

public Long getSex() {
return sex;
}

public void setSex(Long sex) {
this.sex = sex;
}

public java.sql.Date getBirthday() {
return birthday;
}

public void setBirthday(java.sql.Date birthday) {
this.birthday = birthday;
}

public String getHometown() {
return hometown;
}

public void setHometown(String hometown) {
this.hometown = hometown;
}

public String getProfession() {
return profession;
}

public void setProfession(String profession) {
this.profession = profession;
}

public Long getSingle() {
return single;
}

public void setSingle(Long single) {
this.single = single;
}

public String getConstellation() {
return constellation;
}

public void setConstellation(String constellation) {
this.constellation = constellation;
}

public String getMobile() {
return mobile;
}

public void setMobile(String mobile) {
this.mobile = mobile;
}

public String getSignture() {
return signture;
}

public void setSignture(String signture) {
this.signture = signture;
}

public java.sql.Timestamp getCreate_time() {
return create_time;
}

public void setCreate_time(java.sql.Timestamp create_time) {
this.create_time = create_time;
}
}

Mapper

@Mapper
public interface UsersMapper {

@Select("SELECT  id, name, email from users WHERE id=#{id}")
Users findUserById(Integer id);

/**
* 根据openid 获得用户信息
* @param openid
* @return
*/
@Select("SELECT * FROM  users WHERE openid=#{openid}")
Users getUserByOpenId(String openid);

/**
* 模糊查询用户信息
* @param username
* @return
*/
@Select("SELECT * FROM  users WHERE username LIKE #{username}")
List<Users> getUserByUserName(String username);

/**
* 新增加一个用户
* @param user
*/
@Insert("INSERT INTO users (openid,username,sex,birthday,hometown,profession,single,constellation,mobile,signture,create_time) VALUES (#{openid},#{username},#{sex},#{birthday},#{hometown},#{profession},#{single},#{constellation},#{mobile},#{signture},#{create_time})")
//设置id自增长
@Options(useGeneratedKeys=true,keyColumn="id",keyProperty="id")
void insert(Users user);

@Update("UPDATE users SET hometown=#{hometown} WHERE id=#{id}")
void update(Users user);

@Delete("DELETE FROM users WHERE id =#{id}")
void delete(Long id);
}

接口上定义 @Mapper 的注解,就可以被 Spring 容器扫描到,如果觉得每个接口都需要定义繁琐,也可以在程序的入口点使用 @MapperScan 注解进行 Mapper 的包扫描

@SpringBootApplication
@MapperScan("cn.globalrave.barweb.mapper")
public class BarWebApplication {
public static void main(String[] args) {
SpringApplication.run(BarWebApplication.class, args);
}
}

单元测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTests {

@Autowired
private UsersMapper userMapper;

@Test
public void TestMyBatisUsers() throws Exception {
Users user = new Users();
user.setUsername("irving");
user.setOpenid("456798091232322");
user.setHometown("IT 民工");
userMapper.insert(user);
Assert.assertEquals(0, userMapper.getUserByUserName(user.getUsername()).size());
}
}

Spring 声明式事务管理与 @Transactional 注解使用

事务管理对于程序应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。Spring 对事务管理提供了一致的抽象,为不同的事务API提供一致的编程模型,比如 JDBC, Hibernate,MyBatis, JPA 等,支持基于注解的声明式事务管理,不管是 JPA 还是 JDBC 都实现了 PlatformTransactionManager 接口。比如使用的是 spring-boot-starter-jdbc 依赖,框架会默认注入 DataSourceTransactionManager 实例(MyBatis 依赖于JDBC 所以也是使用的 org.springframework.jdbc.datasource.DataSourceTransactionManager)。使用是 spring-boot-starter-data-jpa 依赖,框架会默认注入 JpaTransactionManager 实例。

可以通过如下代码,查看当前项目使用了哪个实例

@EnableSwagger2Doc
@SpringBootApplication // same as @Configuration @EnableAutoConfiguration @ComponentScan
@EnableTransactionManagement
@MapperScan("cn.globalrave.barweb.mapper")
public class BarWebApplication {

private final Logger logger = LoggerFactory.getLogger(this.getClass());

@Bean
public Object getPlatformTransactionManagerBean(PlatformTransactionManager platformTransactionManager){
logger.info(platformTransactionManager.getClass().getName());
return new Object();
}
public static void main(String[] args) {
SpringApplication.run(BarWebApplication.class, args);
}
}

声明式事务管理建立在 AOP (依赖aopalliance.jar包)之上的。本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中,这正是 Spring 倡导的非侵入式的开发方式。

配置声明式事务管理也有两种常用的方式,一种是基于tx和aop名字空间的xml配置文件,另一种就是基于 @Transactional 注解。

基于tx和aop名字空间的 xml 配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="com.test.mybatis.mapper" />
<context:property-placeholder location="classpath:jdbc.properties" />
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource"
p:driverClassName="${jdbc.driver}"
p:url="${jdbc.url}"
p:username="${jdbc.user}"
p:password="${jdbc.password}"/>

<bean id="sqlSessionFactory"
class="org.mybatis.spring.SqlSessionFactoryBean"
p:dataSource-ref="dataSource"
p:configLocation="classpath:mybatisConfig.xml"
p:mapperLocations="classpath:com/test/mybatis/mapper/*.xml"/>
<!-- mybatis sqlSession -->
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
<constructor-arg index="0" ref="sqlSessionFactory" />
</bean>
<!-- 事务管理器配置 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 事务传播配置 -->
<tx:advice id="transactionAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="save" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="delete*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="update*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="get*" read-only="true" />
</tx:attributes>
</tx:advice>
<!-- 配置哪些方法参与事务 -->
<aop:config proxy-target-class="true">
<aop:pointcut id="transactionxPointcut"    expression="execution(*com.test.service.*.*(..)" />
<aop:advisor advice-ref="transactionAdvice" pointcut-ref="transactionxPointcut" />
</aop:config>
</beans>

让 Spring 管理 MyBatis 中的事务,无需额外配置,只需要设置 MyBatis 中的 org.mybatis.spring.SqlSessionFactoryBean 引用的数据源与 jdbc 中的 DataSourceTransactionManager 引用的数据源一致即可,否则事务管理会不起作用。

基于@Transactional 注解配置

基于注解的方式启用事物很简单,使用注解 @EnableTransactionManagement 开启事务支持后,然后在访问数据库的 Service 层上添加注解 @Transactional 便可(被 @Transactional 注解的方法,将支持事务。如果注解在类上,则整个类的所有方法都默认支持事务)。

启动类
@EnableSwagger2Doc
@SpringBootApplication // same as @Configuration @EnableAutoConfiguration @ComponentScan
@EnableTransactionManagement
@MapperScan("cn.globalrave.barweb.mapper")
public class BarWebApplication {
public static void main(String[] args) {
SpringApplication.run(BarWebApplication.class, args);
}
}

Service 类

@Service
public class UsersService implements IUsersService {

@Autowired
private UsersMapper usersMapper;

@Autowired
private TagsMapper tagsMapper;

@Autowired
private ImgsMapper imgsMapper;

@Override
public Users getUserByOpenId(String openId) {
return this.usersMapper.getUserByOpenId(openId);
}

@Override
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,timeout=36000,rollbackFor=Exception.class)
public Boolean bindWeixinUser(WeixinBindRequest request) {
//...
}
}

是不是一切都很简单,这些 Spring Boot 为我们自动做了, 如果项目中使用了多个持久化框架(比如 JPA + MyBatis),还可以指定使用哪个事务管理器。

@EnableTransactionManagement
@SpringBootApplication
public class BarApplication implements TransactionManagementConfigurer {

@Resource(name="txManager2")
private PlatformTransactionManager txManager2;

// 事务管理器1
@Bean(name = "txManager1")
public PlatformTransactionManager txManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}

// 事务管理器2
@Bean(name = "txManager2")
public PlatformTransactionManager txManager2(EntityManagerFactory factory) {
return new JpaTransactionManager(factory);
}

// 设置默认的事务管理器
@Override
public PlatformTransactionManager annotationDrivenTransactionManager() {
return txManager2;
}

public static void main(String[] args) {
SpringApplication.run(BarApplication.class, args);
}
}

使用的时候指定好名称就 OK 了

@Transactional(value="txManager1")
public void findUserPassword(String userName) {
//get user
}

MyBatis 配置打印 SQL 日志

配置日志也很简单,只需要在在 logback 增加如下配置
<!-- mybatis 日志 -->
<logger name="cn.globalrave.barweb.mapper" level="DEBUG"/>
<logger name="org.mybatis">
<level value="DEBUG"/>
</logger>

配置OK后,比如打印出事物的日志

[ INFO ] [115236 [http-nio-127.0.0.1-5000-exec-1]] [2017-09-03 18:50:25] com.alibaba.druid.pool.DruidDataSource [854] - {dataSource-2} inited
[ DEBUG] [115250 [http-nio-127.0.0.1-5000-exec-1]] [2017-09-03 18:50:25] org.mybatis.spring.SqlSessionUtils [97] - Creating a new SqlSession
[ DEBUG] [115260 [http-nio-127.0.0.1-5000-exec-1]] [2017-09-03 18:50:25] org.mybatis.spring.SqlSessionUtils [128] - Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@33ba62e0]
[ DEBUG] [115268 [http-nio-127.0.0.1-5000-exec-1]] [2017-09-03 18:50:25] org.mybatis.spring.transaction.SpringManagedTransaction [87] - JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@25a7562e] will be managed by Spring
[ DEBUG] [115271 [http-nio-127.0.0.1-5000-exec-1]] [2017-09-03 18:50:25] cn.globalrave.barweb.mapper.ActivityMapper.getActivityById [159] - ==>  Preparing: select * from activity where id=?
[ DEBUG] [115287 [http-nio-127.0.0.1-5000-exec-1]] [2017-09-03 18:50:25] cn.globalrave.barweb.mapper.ActivityMapper.getActivityById [159] - ==> Parameters: 0(Integer)
[ DEBUG] [115316 [http-nio-127.0.0.1-5000-exec-1]] [2017-09-03 18:50:25] cn.globalrave.barweb.mapper.ActivityMapper.getActivityById [159] - <==      Total: 1
[ DEBUG] [115316 [http-nio-127.0.0.1-5000-exec-1]] [2017-09-03 18:50:25] org.mybatis.spring.SqlSessionUtils [186] - Releasing transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@33ba62e0]
[ DEBUG] [115317 [http-nio-127.0.0.1-5000-exec-1]] [2017-09-03 18:50:25] org.mybatis.spring.SqlSessionUtils [284] - Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@33ba62e0]
[ DEBUG] [115317 [http-nio-127.0.0.1-5000-exec-1]] [2017-09-03 18:50:25] org.mybatis.spring.SqlSessionUtils [310] - Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@33ba62e0]
[ DEBUG] [115317 [http-nio-127.0.0.1-5000-exec-1]] [2017-09-03 18:50:25] org.mybatis.spring.SqlSessionUtils [315] - Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@33ba62e0]
[ INFO ] [115415 [http-nio-127.0.0.1-5000-exec-1]] [2017-09-03 18:50:25] cn.globalrave.barweb.controller.ActivityController [31] - {"description":"没有嘈杂的喧闹,没有炫目的灯光,更多的是一份安静,一份温暖","end":1503811180000,"id":0,"recommend":"1","start":1503846754000,"subject":" 复古爵士邂逅老上海风情","title":"复古爵士邂逅老上海风情"}

其他组件

上述系统整理了 Spring Boot 整合 MyBatis 的一些配置,实际开发中其实有很多的 GRUD 与分页操作,可以集成 Mybatis-Plus 这个组件

<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatisplus-spring-boot-starter</artifactId>
<version>1.0.4</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>2.1.0</version>
</dependency>
Mapper 继承 BaseMapper 即有了常用的 GRUD 的操作了,是不是很方便。


@Mapper
public interface ActivityMapper extends BaseMapper<Activity> {

/**
* 根据openid 获得用户信息
* @param id
* @return
*/
@Select("select * from activity where id=#{id}")
Activity getActivityById(Integer id);
}

public interface IActivityService extends IService<Activity> {
}

@Service
public class ActivityServiceImpl extends ServiceImpl<ActivityMapper, Activity> implements IActivityService {

private final Logger logger = LoggerFactory.getLogger(this.getClass());

@Autowired
protected ActivityMapper activityMapper;

@Transactional(readOnly=true)
@Override
public Activity getActivityById(int id) {
return this.activityMapper.selectById(id);
}

@Override
public List<Activity> getActivityList() {
List<Activity> list= this.activityMapper.selectList(new EntityWrapper());
return list;
}
}

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: