no11-mybatis-plus和事务
分类: springboot MySQL 专栏: springboot学习 标签: mybatis-plus
2023-03-28 16:22:06 1508浏览
注意:mybatis驼峰命名开关默认是关闭的false,mybatis-plus默认是开启的true
springboot整合mybatis
- pom引入相关依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.3</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
- 配置文件
spring.datasource.url=jdbc:mysql:///springdata-test?characterEncoding=utf8&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=123456 mybatis.mapper-locations=classpath:mapper/*.xml logging.level.com.afan.demo.dao=debug
- dao接口
@Mapper public interface StudentDao {
补充:
- @MapperScan
- mapper接口和sql映射文件在一个包下的情况
mybatis-plus
基本用法
- 1.pom引入依赖
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.2</version> </dependency>
- 2.dao接口
@Mapper public interface AddressMapper extends BaseMapper<Address> { }
- 3.配置文件
spring.datasource.url=jdbc:mysql:///springdata-test?characterEncoding=utf8&serverTimezone=Asia/Shanghai spring.datasource.username=root spring.datasource.password=123456 #控制台打印sql #logging.level.com.xiaojie.ch6mybatisplus.dao=debug #控制台打印sql语句的另外一种写法 mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl #驼峰命名——默认是开启的true mybatis-plus.configuration.map-underscore-to-camel-case=true
- 4.测试类
@Test void contextLoads() { List<Address> students = addressMapper.selectList(null); students.forEach(s-> System.out.println(s)); } @Test void add(){ Address address = new Address().setCity("南京").setArea("bb").setProvince("江苏").setDetailedAddress("xxxx"); int insert = addressMapper.insert(address); System.out.println(insert); System.out.println(address.getId()); }
- 5.插入数据的时候注意
Mybatis-puls默认添加添加对象时,采用分布式唯一 ID, 根据雪花算法得出长度为19的唯一 ID插入表中,则不建议表中Id为int类型。
解决方案1:将表ID改为VARCHAR类型,实体类改为String类型,采用雪花算法,并且可以立即获得新增后的最新雪花Id。
解决方案2:不采用雪花算法生成Id,实体类添加@TableId注解指定ID生成策略,即可存入自增的ID
public class Address { @TableId(type = IdType.AUTO) private String id;
雪花算法介绍:
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。 其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID (5个bit是数据中心,5个bit的机器ID), 12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生4096个ID),最后还有一个符号位,永远是0。具体实现的代码可以参看https://github.com/twitter/snowflake.雪花算法支持的TPS可以达到419万左右(2^12*1000) 几乎保证全球唯一。
分页
@Configuration public class PageConfig { @Bean MybatisPlusInterceptor mybatisPlusInterceptor (){ MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return mybatisPlusInterceptor; } }
@Test void testPage(){ // Page<Address> page = new Page<>(2,2);//分页,加个排序 Page<Address> page = new Page<>(1,2);//分页, page.addOrder(OrderItem.desc("id"));//加个排序 降序 Page<Address> page1 = addressMapper.selectPage(page, null); System.out.println(page1.getRecords()); }
补充:采用pagehelper分页插件做
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.3.0</version> </dependency>
然后就跟之前写法一样了。
@Test void testPageHelper(){ PageHelper.startPage(1,2,"id desc"); List<Address> addresses = addressMapper.selectList(null); PageInfo<Address> pageInfo = new PageInfo<>(addresses,2); System.out.println(pageInfo); }
条件查询
- 1.map的方式
@Test void testMap(){ Map map = new HashMap(); map.put("province","湖南省"); List list = addressMapper.selectByMap(map); list.forEach(System.out::println); }
- 2.QueryWrapper的方式
@Test void testSelectBy(){ //这种方式的话跟上面的map差不多 // Address address = new Address(); // address.setCity("岳阳市"); // QueryWrapper<Address> queryWrapper = new QueryWrapper<>(address); // QueryWrapper<Address> queryWrapper = new QueryWrapper<>(); // // queryWrapper.like("province","湖南") // .eq("id",2); //查询ID在某个范围,且名字城市名字左模糊匹配,只赋值id字段 QueryWrapper<Address> queryWrapper1 = new QueryWrapper(); queryWrapper1.between("id",3,4) .like("city","岳阳") .select("id"); List<Address> list = addressMapper.selectList(queryWrapper1); list.forEach(System.out::println); }
- 3.LambdaQueryWrapper
3.1分页带条件查询
/** * 分页带条件查询老师信息 * @param pageNo * @param o * @return */ public PageInfo teacherPage(int pageNo, Teacher o) { PageHelper.startPage(pageNo, Sys.pageSize,"id desc"); LambdaQueryWrapper<Teacher> lambdaQueryWrapper = Wrappers.lambdaQuery(); //条件查询 if (Objects.nonNull(o.getRealName()) && o.getRealName().trim().length()>0) { lambdaQueryWrapper.like(Teacher ::getRealName, o.getRealName()); } if (Objects.nonNull(o.getState())) { //// 1. 其中Teacher::getState的意思就相当于: // 1.1 实例化一个Teacher对象 // Teacher teacher = new Teacher; // 1.2 调用对象Teacher的get方法,这里调用的是getState: // teacher.getState(); // 2.eq方法相当于赋值“=” // 即将state的值为参数o.getState(),注意此时使用的是get方法而不是set方法 lambdaQueryWrapper.eq(Teacher ::getState, o.getState()); } if (Objects.nonNull(o.getTeaNo()) && o.getTeaNo().trim().length()>0) { lambdaQueryWrapper.eq(Teacher ::getTeaNo, o.getTeaNo()); } List<Teacher> li = teacherDao.selectList(lambdaQueryWrapper); PageInfo<Teacher> pageInfo=new PageInfo(li,Sys.pageSize); return pageInfo; }
3.2.不分页排序
lambdaQueryWrapper.orderByDesc(StoreConsumer::getRegisterTime);
querywrapper查询条件构造器
物理删除和逻辑删除
Mybatis-plus物理删除:在数据库中移除。逻辑删除:数据库中没有移除,而是在代码中使用一个变量来使他失效! (如: delete=0=>delete=1;)
方案:给数据表增加一个字段:is_del,用于表示该数据是否被逻辑删除,初始值为 0。0 表示未删除, 1 表示删除。
- 1.方法一:实体类字段上加上注解
- value = “未删除的值,默认值为0”;若设置为2,则查询时 where 后面自动拼接 is_del = 2
- delval = “删除后的值,默认值为1”
@TableField(fill = FieldFill.INSERT) @ApiModelProperty("是否删除标识 0:未删除 1:删除") @TableLogic(value = "0",delval = "1") private int isDel;
执行删除,原 deleteById 会物理删除,现在实现逻辑删除
testMapper.deleteById(id);
执行删除后,id 对应行数据的 is_del 字段会变成 1
由于 isDel 添加了 @TableLogic 注解,MyBatis Plus 做所有查询操作时,自动会在 where 条件后面拼上 is_del = 0,所以 is_del 置一后,实现了逻辑删除
思考:一般情况下,我们表设计会有两个时间字段,一个时创建时间,另一个是最后更新时间;通过最后更新时间我们可以知道该数据被删除的时间,那么方法一 deleteById 后,同时更新最后修改时间吗?
答:不可以,如需改变最后修改时间,请选择方法二
- 2.方法二
思路就是通过 update 方法来把 is_del 从 0 更新为 1,通过 MyBatis Plus 提供的自动填充功能,可以自动更新最后修改时间和最后修改人
配置自动填充
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; import org.apache.commons.lang3.StringUtils; import org.apache.ibatis.reflection.MetaObject; import org.springframework.stereotype.Component; import java.time.LocalDateTime; @Component public class MyObjectHandler implements MetaObjectHandler { @Override public void insertFill(MetaObject metaObject) { //属性名称,不是字段名称 this.setFieldValByName("CreateTime", new Date(), metaObject); this.setFieldValByName("ModifiedTime", new Date(), metaObject); } @Override public void updateFill(MetaObject metaObject) { this.setFieldValByName("ModifiedTime", new Date(), metaObject); } }
@ApiModelProperty(value = "创建时间") @TableField(fill = FieldFill.INSERT) private Date CreateTime; @ApiModelProperty(value = "更新时间") @TableField(fill = FieldFill.INSERT_UPDATE) private Date ModifiedTime;
service 层执行,找出 id 对应的一行,然后把 is_del 字段更新成 1
public void deleteById(Long id) { LambdaUpdateWrapper<TestPO> updateWrapper = new UpdateWrapper<TestPO>().lambda(); updateWrapper.set(TestPO::getIsDel, 1).eq(TestPO::getId, id); baseMapper.update(new TestPO(), updateWrapper); }
此时最后修改人和最后修改时间等于删除人和删除时间,完美!
service层
/** * service接口需要集成IService */ public interface AddressService extends IService<Address> { } /** * service实现类需要继承ServiceImpl,并且实现service接口 */ @Service public class AddressServiceImpl extends ServiceImpl<AddressMapper, Address> implements AddressService { }
crud接口
@RestController @RequestMapping("/address") public class AddressController { @Autowired private IAddressService addressService; @PostMapping("/insert") public boolean save(@RequestBody Address address){ return addressService.save(address); } @DeleteMapping("/remove/{id}") public boolean remove(@PathVariable("id") Integer id){ return addressService.removeById(id); } @PutMapping("/saveOrUpdate") public boolean saveOrUpdate(@RequestBody Address address){ return addressService.saveOrUpdate(address); } @GetMapping("/list") public List<Address> remove(){ QueryWrapper<Address> addressQueryWrapper = new QueryWrapper<>(); addressQueryWrapper.eq("province", "湖北省"); return addressService.list(addressQueryWrapper); } @GetMapping("/page") public IPage<Address> pageAddress(){ Page<Address> page = new Page<>(1,3); QueryWrapper<Address> addressQueryWrapper = new QueryWrapper<>(); //构造查询条件 addressQueryWrapper.eq("province", "湖北省"); IPage<Address> addressPage = addressService.page(page, addressQueryWrapper); return addressPage; } }
多表联查
- 传统的方式:一旦涉及到多表查询就改用mybatis的sql映射文件的方式
- mybatis-plus-join工具
参考文章:https://mp.weixin.qq.com/s/IOWlaCwlTbYtPdAnvqbxkA
public void page() { IPage<OrderDto> orderPage = orderMapper.selectJoinPage( new Page<OrderDto>(2,10), OrderDto.class, new MPJLambdaWrapper<Order>() .selectAll(Order.class) .select(Product::getUnitPrice) .selectAs(User::getName, OrderDto::getUserName) .selectAs(Product::getName, OrderDto::getProductName) .leftJoin(User.class, User::getId, Order::getUserId) .leftJoin(Product.class, Product::getId, Order::getProductId) .orderByAsc(Order::getId)); orderPage.getRecords().forEach(System.out::println); }
逆向生成代码
- 方式一:编写自动生成的代码
下面这个是生成mybatis_plus的代码
- 先在数据库里把表建好
- pom引入相关依赖
<!--代码生成器依赖--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.4.0</version> </dependency> <!--mybatis-plus代码模板默认使用velocity--> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.2</version> </dependency>
- 自动生成配置类代码
/** * Mybatis代码生成器 */ public class MybatisPlusGenerator { public static void main(String[] args) { // 代码生成器 AutoGenerator autoGenerator = new AutoGenerator(); //1、 全局配置 GlobalConfig globalConfig = new GlobalConfig(); //项目绝对路径 String projectPath = "E:\\desk\\讲课\\springboot讲解\\springboot_study\\part6_mybatisplus"; // String projectPath = "E:/springboot-writing-books/项目目录/springboot-book/springboot-mybatis"; globalConfig.setOutputDir(projectPath + "/src/main/java"); globalConfig.setAuthor("wangxiaojie"); globalConfig.setOpen(false); // gc.setSwagger2(true); 实体属性 Swagger2 注解 autoGenerator.setGlobalConfig(globalConfig); //2、 数据源配置 DataSourceConfig dataSourceConfig = new DataSourceConfig(); dataSourceConfig.setUrl("jdbc:mysql://localhost:3306/springdata-test?characterEncoding=utf-8&serverTimezone=Asia/Shanghai"); // dataSourceConfig.setSchemaName("public"); dataSourceConfig.setDriverName("com.mysql.cj.jdbc.Driver"); dataSourceConfig.setUsername("root"); dataSourceConfig.setPassword("123456"); autoGenerator.setDataSource(dataSourceConfig); // 包配置 PackageConfig packageConfig = new PackageConfig(); //在controller service mapper mapper.xml等这类文件外层创建一个文件夹 //packageConfig.setModuleName("address"); packageConfig.setModuleName(""); packageConfig.setParent("com.jf3q.part6_mybatisplus"); autoGenerator.setPackageInfo(packageConfig); // 自定义配置 InjectionConfig injectionConfig = new InjectionConfig() { @Override public void initMap() { // to do nothing } }; // 如果模板引擎是 freemarker //String templatePath = "/templates/mapper.xml.ftl"; // 默认模板引擎是 velocity String templatePath = "/templates/mapper.xml.vm"; // 自定义输出配置 List<FileOutConfig> fileOutConfigList = new ArrayList<>(); // 自定义配置会被优先输出 fileOutConfigList.add(new FileOutConfig(templatePath) { @Override public String outputFile(TableInfo tableInfo) { // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!! return projectPath + "/src/main/resources/mapper/" + packageConfig.getModuleName() + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML; } }); /*injectionConfig.setFileCreate(new IFileCreate() { @Override public boolean isCreate(ConfigBuilder configBuilder, FileType fileType, String filePath) { // 判断自定义文件夹是否需要创建 checkDir("调用默认方法创建的目录,自定义目录用"); if (fileType == FileType.MAPPER) { // 已经生成 mapper 文件判断存在,不想重新生成返回 false return !new File(filePath).exists(); } // 允许生成模板文件 return true; } });*/ injectionConfig.setFileOutConfigList(fileOutConfigList); autoGenerator.setCfg(injectionConfig); // 配置模板 TemplateConfig templateConfig = new TemplateConfig(); // 配置自定义输出模板 //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别 // templateConfig.setEntity("templates/entity2.java"); // templateConfig.setService(); // templateConfig.setController(); templateConfig.setXml(null); autoGenerator.setTemplate(templateConfig); // 策略配置 StrategyConfig strategy = new StrategyConfig(); strategy.setNaming(NamingStrategy.underline_to_camel); strategy.setColumnNaming(NamingStrategy.underline_to_camel); //strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!"); strategy.setEntityLombokModel(true); strategy.setRestControllerStyle(true); // 公共父类 //strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!"); // 写于父类中的公共字段 //strategy.setSuperEntityColumns("id"); //strategy.setInclude(scanner("表名,多个英文逗号分割").split(",")); String tableNames = "student,product,address,order,order_item"; //表名,逗号分隔 strategy.setInclude(tableNames.split(",")); strategy.setControllerMappingHyphenStyle(true); strategy.setTablePrefix(packageConfig.getModuleName() + "_"); autoGenerator.setStrategy(strategy); //设置模板引擎 autoGenerator.setTemplateEngine(new VelocityTemplateEngine()); //选择了非默认引擎Velocity,需要在 AutoGenerator 中 设置模板引擎。 /*autoGenerator.setTemplateEngine(new FreemarkerTemplateEngine()); autoGenerator.setTemplateEngine(new BeetlTemplateEngine()); // set custom engine (reference class is your custom engine class) autoGenerator.setTemplateEngine(new CustomTemplateEngine());*/ autoGenerator.execute(); } }
- easycode插件生成
- mybatisx插件生成
事务
事务的定义与特性
数据库事务可以包含一个或者多个数据库操作,但这些操作构成一个逻辑上的整体,这个逻辑整体中的数据库操作,要么全部执行成功,要么全部不执行,也就是说构成事务的所有操作,要么全都对数据产生影响,要么全都不产生影响,不管事务是否执行成功,数据库总是保持一致性的状态。
事务的特性:原子性(Atomicity),一致性(Consistency),隔离性(Isolation),持久性(Durability),简称ACID
- 原子性:事务所有操作作为一个整体,像原子一样不可分割。要么全部成功,要么全部失败
- 一致性:事务的执行结果必须使数据库从一个一致性的状态到另一个一致性的状态。一致性的状态是指系统的状态满足数据完整性约束(主码,参照完整性,check约束等),并且系统的状态反应数据库本应描述的显示的真实状态,比如银行转账之后,互相转账的两个账户金额总和保持不变。
- 隔离性:并发执行的事务不会相互影响,其对数据库的影响和它们串行执行时一样,比如多个用户同时往一个账户转账,最后账户的结果应该和他们按先后次序转账的结果一样。
- 持久性:事务一旦提交,其对数据库的更新就是持久的,任何事务或系统故障都不会导致数据丢失,不会因为系统故障或者断电造成数据不一致或者丢失。
事务的并发问题——隔离级别
事务的并发问题:一个数据库中的一份数据,由于被多客户端并发访问,或者被多线程并发访问,如果没有采取必要的隔离措施,就可能导致一些问题:比如脏读,不可重复读,幻读,丢失更新
查询数据库的隔离级别:
select @@transaction_isolation;
切换隔离级别:
set session transaction_isolation='read-uncommitted'; set session transaction_isolation='read-committed'; set session transaction_isolation='repeatable-read'; set session transaction_isolation='serializable';
begin; select * from tb_user; update tb_user set balance=balance-100 where id =1; update tb_user set balance=balance+100 where id=2; rollback;
脏读事例:
事务A读取了事务B更新的数据,然后B回滚操作,那么A读取的数据据是脏数据。
时间节点 | 事务A | 事务B |
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为500元 | |
T4 | 存入1000元 ,成为vip | |
T5 | 查询账户余额为1500元(脏读)给发了100优惠券 | |
T6 | 事务回滚(余额恢复为500元) | |
T7 | 提交 |
这个案例导致这100的优惠券拿不回来了
不可重复读
一个事务中两次(不同时间点)读取同一行数据,但这两次读取到的数据不一致。
时间节点 | 取款事务A | 查询事务B |
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询账户余额为500 | |
T4 | 查询账户余额500 | |
T5 | 取出200(余额更新为300元) | |
T6 | 提交事务 | |
T7 | 查询账户余额为300元 | |
T8 | 提交事务 |
在这个场景中,事务B在事务A取款前后读取的账户余额不一致。
造成的危害:假设b事务是一个很大的事务,a事务是一个很小的事务(这种小事务可能很多),那么b事务的程序里面一堆 if 怎样再怎样,每次读的东西都不一样,你到底想怎样。
幻读
T1读取了一个字段,T2对字段进行了插入。此时T1再次读取时仍然为原来的数据,不过T1如果对T2插入行同样进行插入时就会报错。
时间节点 | 事务 A | 事务 B |
T1 | 开始事务 | |
T2 | 查询所有用户(假设没有id为2的) | |
T3 | 开始事务 | |
T4 | (捷足先登)新增一个用户 | |
T5 | 提交事务 | |
T6 | 新增一个用户id为2失败(幻读) |
可以理解成:幻读是不可重复读的一种特殊场景
分为编程式事务和声明式事务
@Autowired private BankMapper bankMapper; @Autowired private TransactionTemplate transactionTemplate; @Override @Transactional public boolean transfer(Integer staffId, Integer fromCustomerId, Integer toCustomerId, BigDecimal money) { BankCustomer fromCustomer = bankMapper.selectBankCustomerById(fromCustomerId); BankCustomer toCustomer = bankMapper.selectBankCustomerById(toCustomerId); //第一步:给转账客户扣钱 bankMapper.updateBankAccountBalance(fromCustomer.getBankAccount().getId(),money.negate()); //模拟交易过程中发生异常 //int a = 1/0; //第二步:记录转账客户流水 BankOperate fromOperate = new BankOperate(); fromOperate.setCustomerId(fromCustomerId); fromOperate.setStaffId(staffId); fromOperate.setType(1); fromOperate.setLastBalance(fromCustomer.getBankAccount().getBalance()); fromOperate.setBalance(fromCustomer.getBankAccount().getBalance().subtract(money)); fromOperate.setMoney(money); Date date1 = new Date(); fromOperate.setCreateTime(date1); fromOperate.setUpdateTime(date1); bankMapper.insertBankOperate(fromOperate); //第三步:给被转账客户加钱 bankMapper.updateBankAccountBalance(toCustomer.getBankAccount().getId(),money); //第四步:记录被转账客户流水 BankOperate toOperate = new BankOperate(); toOperate.setCustomerId(toCustomerId); toOperate.setStaffId(staffId); toOperate.setType(0); toOperate.setLastBalance(toCustomer.getBankAccount().getBalance()); toOperate.setBalance(toCustomer.getBankAccount().getBalance().add(money)); toOperate.setMoney(money); Date date2 = new Date(); toOperate.setCreateTime(date2); toOperate.setUpdateTime(date2); bankMapper.insertBankOperate(toOperate); return true; //编程式事务 /*return transactionTemplate.execute(new TransactionCallback<Boolean>() { @Override public Boolean doInTransaction(TransactionStatus transactionStatus) { try { //第一步:给转账客户扣钱 bankMapper.updateBankAccountBalance(fromCustomer.getBankAccount().getId(),money.negate()); //模拟交易过程中发生异常 //int a = 1/0; //第二步:记录转账客户流水 BankOperate fromOperate = new BankOperate(); fromOperate.setCustomerId(fromCustomerId); fromOperate.setStaffId(staffId); fromOperate.setType(1); fromOperate.setLastBalance(fromCustomer.getBankAccount().getBalance()); fromOperate.setBalance(fromCustomer.getBankAccount().getBalance().subtract(money)); fromOperate.setMoney(money); Date date1 = new Date(); fromOperate.setCreateTime(date1); fromOperate.setUpdateTime(date1); bankMapper.insertBankOperate(fromOperate); //第三步:给被转账客户加钱 bankMapper.updateBankAccountBalance(toCustomer.getBankAccount().getId(),money); //第四步:记录被转账客户流水 BankOperate toOperate = new BankOperate(); toOperate.setCustomerId(toCustomerId); toOperate.setStaffId(staffId); toOperate.setType(0); toOperate.setLastBalance(toCustomer.getBankAccount().getBalance()); toOperate.setBalance(toCustomer.getBankAccount().getBalance().add(money)); toOperate.setMoney(money); Date date2 = new Date(); toOperate.setCreateTime(date2); toOperate.setUpdateTime(date2); bankMapper.insertBankOperate(toOperate); return true; } catch (RuntimeException e) { // 遇到运行时异常就回滚 transactionStatus.setRollbackOnly(); return false; } } });*/ }
事务的传播行为
对事务的要求程度可以从大到小排序:mandatory / supports / required / requires_new / nested / not supported / never
只读事务和查询事务:采用supports
增删改事务:采用required
参考网址:https://blog.csdn.net/qq_38262266/article/details/108709840
参考视屏:https://www.bilibili.com/video/BV1R8411c7m2
事务配置
事务常见配置包括如下表所示:
属性 | 作用 | 示例 |
readOnly | 设置是否为只读事务 | readOnly=true 只读事务 |
timeout | 设置事务超时时间 | timeout = -1(永不超时) |
rollbackFor | 设置事务回滚异常(class) | rollbackFor = {NullPointException.class} |
rollbackForClassName | 设置事务回滚异常(String) | 同上格式为字符串 |
noRollbackFor | 设置事务不回滚异常(class) | noRollbackFor = {NullPointException.class} |
noRollbackForClassName | 设置事务不回滚异常(String) | 同上格式为字符串 |
propagation | 设置事务传播行为 | propagation = Propagation.REQUIRED 有7种…… |
好博客就要一起分享哦!分享海报
此处可发布评论
评论(3)展开评论