SSM整合——Springboot
1.0 概述
1.1 持久层:
DAO层(mapper)
-
DAO层:DAO层主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装在此
-
DAO层的设计首先是设计DAO的接口,
-
然后在spring-mapper.xml的配置文件中定义此接口的实现类,
-
然后就可在模块中调用此接口来进行数据业务的处理,而不用关心此接口的具体实现类是哪个类,显得结构非常清晰,
-
DAO层的数据源配置,以及有关数据库连接的参数都在spring-mapper的配置文件中进行配置。
-
1.2 业务层:
Service层
-
Service层:Service层主要负责业务模块的逻辑应用设计。
-
首先设计接口,再设计其实现的类
-
接着再在Spring的配置文件中配置其实现的关联。这样我们就可以在应用中调用Service接口来进行业务处理
-
Service层的业务实现,具体要调用到已定义的DAO层的接口
-
数据库的事务配置在service.xml中配置
-
封装Service层的业务逻辑有利于通用的业务逻辑的独立性和重复利用性,程序显得非常简洁。
-
1.3 表现层:
Controller层(Handler层)
-
Controller层:Controller层负责具体的业务模块流程的控制,
-
在此层里面要调用Service层的接口来控制业务流程,
-
控制的配置也同样是在springmvc.xml的配置文件里面进行,针对具体的业务流程,会有不同的控制器,
-
我们具体的设计过程中可以将流程进行抽象归纳,设计出可以重复利用的子单元流程模块,
这样不仅使程序结构变得清晰,也大大减少了代码量
-
1.4 View层
-
View层 此层与控制层结合比较紧密,需要二者结合起来协同工发。View层主要负责前台jsp页面的表示.
1.5 各层联系
-
DAO层,Service层这两个层次都可以单独开发,互相的耦合度很低,完全可以独立进行,这样的一种模式在开发大项目的过程中尤其有优势
-
Controller,View层因为耦合度比较高,因而要结合在一起开发,但是也可以看作一个整体独立于前两个层进行开发。这样,在层与层之前我们只需要知道接口的定义,调用接口即可完成所需要的逻辑单元应用,一切显得非常清晰简单。
-
Service逻辑层设计
-
Service层是建立在DAO层之上的,建立了DAO层后才可以建立Service层,而Service层又是在Controller层之下的,因而Service层应该既调用DAO层的接口,又要提供接口给Controller层的类来进行调用,它刚好处于一个中间层的位置。每个模型都有一个Service接口,每个接口分别封装各自的业务处理方法。
-
2.0 SSM整合
2.1 创建SpringBoot项目
2.2 引入依赖
<properties>
? ? ? ?<java.version>11</java.version>
? ? ? ?<mybatis.version>2.2.2</mybatis.version>
? ?</properties>
? ?
? ?<dependencies>
? ? ? ?<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>${mybatis.version}</version>
? ? ? ?</dependency>
?
? ? ? ?<dependency>
? ? ? ? ? ?<groupId>org.springframework.boot</groupId>
? ? ? ? ? ?<artifactId>spring-boot-devtools</artifactId>
? ? ? ? ? ?<scope>runtime</scope>
? ? ? ? ? ?<optional>true</optional>
? ? ? ?</dependency>
? ? ? ?
? ? ? ?<dependency>
? ? ? ? ? ?<groupId>com.mysql</groupId>
? ? ? ? ? ?<artifactId>mysql-connector-j</artifactId>
? ? ? ? ? ?<scope>runtime</scope>
? ? ? ?</dependency>
? ? ? ?
? ? ? ?<dependency>
? ? ? ? ? ?<groupId>org.projectlombok</groupId>
? ? ? ? ? ?<artifactId>lombok</artifactId>
? ? ? ? ? ?<optional>true</optional>
? ? ? ?</dependency>
? ? ? ?
? ? ? ?<dependency>
? ? ? ? ? ?<groupId>org.springframework.boot</groupId>
? ? ? ? ? ?<artifactId>spring-boot-starter-test</artifactId>
? ? ? ? ? ?<scope>test</scope>
? ? ? ?</dependency>
? ?</dependencies>
注意:引入 spring-boot-devtools 依赖后,每次我们只需要重新编译源代码即可,不要需要重启springboot项目
2.3 工程搭建
#启动端口
server.port=8088
#数据源
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql:///220706a
spring.datasource.username=root
spring.datasource.password=123456
# 日志登记
logging.level.com.whitecamellia.mybatis_demo = debug
#xml配置
mybatis.mapper-locations=classpath:mapper/*Mapper.xml
#驼峰映射
mybatis.configuration.map-underscore-to-camel-case=true
#配置别名
mybatis.type-aliases-package = com.whitecamellia.mybatis_demo.entity
#主键返回
mybatis.configuration.use-generated-keys=true
# 延迟加载
mybatis.configuration.lazy-loading-enabled=true
#二级缓存
mybatis.configuration.cache-enabled=true
2.4?封装Result工具类
package com.whitecamellia.ssm_test.util;
import lombok.Getter;
@Getter
public class Result {
? ?private int code;
? ?private String message;
? ?private Object data;
?
? ?private Result() {
? }
?
? ?public static Result ok () {
? ? ? ?Result result = new Result();
? ? ? ?result.code = 200;
? ? ? ?result.message = "success";
? ? ? ?return result;
? }
?
? ?public static Result ok (Object data) {
? ? ? ?Result result = new Result();
? ? ? ?result.code = 200;
? ? ? ?result.message = "success";
? ? ? ?result.data = data;
? ? ? ?return ?result;
? }
?
? ?public static Result error (int code,String message) {
? ? ? ?Result result = new Result();
? ? ? ?result.code = code;
? ? ? ?result.message = message;
? ? ? ?return ?result;
? }
}
?
?
// 测试
@RestController
public class PersonController {
? ?@Autowired
? ?private PersonService personService;
?
? ?@GetMapping("/findList")
? ?public Result findList () {
? ? ? ?List<Person> list = personService.findList();
? ? ? ?return ?list.size()!= 0 ?
? ? ? ? ? ? ? ?Result.ok(list) :
? ? ? ? ? ? ? ?Result.error(201,"无数据");
? }
}
2.5?优化工具类
@Getter
public class Result<T> {
? ?private int code;
? ?private String message;
? ?private T data;
?
? ?private static final int DEFAULT_SUCCESS_CODE = 200;
? ?private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";
?
? ?private Result() {
? }
?
? ?public static Result<Void> ok () {
? ? ? ?Result<Void> result = new Result<>();
? ? ? ?result.code = DEFAULT_SUCCESS_CODE;
? ? ? ?result.message = DEFAULT_SUCCESS_MESSAGE;
? ? ? ?return result;
? }
?
? ?public static ?T> Result<T> ok (T data) {
? ? ? ?Result<T> result = new Result<>();
? ? ? ?result.code = DEFAULT_SUCCESS_CODE;
? ? ? ?result.message = DEFAULT_SUCCESS_MESSAGE;
? ? ? ?result.data = data;
? ? ? ?return result;
? }
?
? ?public static Result<Void> error (int code,String message) {
? ? ? ?Result<Void> result = new Result<>();
? ? ? ?result.code = code;
? ? ? ?result.message = message;
? ? ? ?return result;
? }
?
}
2.6?测试
@GetMapping("/findList")
? ?public Result<List<Person>> findList () {
? ? ? ?return ?Result.ok(personService.findList());
? }
3.0 分页
3.1 基本使用
@RestController
public class SpAttributeController {
? ?@Autowired
? ?private SpAttributeService spAttributeService;
?
? ?@GetMapping("/find")
? ?public Result<List<SpAttribute>> findByPage() {
? ? ? ?return Result.ok(spAttributeService.findByPage());
? }
}
public interface SpAttributeService {
? ?List<SpAttribute> findByPage();
}
@Service
public class SpAttributeServiceImpl implements SpAttributeService {
?
? ?@Autowired
? ?private SpAttributeMapper spAttributeMapper;
?
? ?@Override
? ?public List<SpAttribute> findByPage() {
? ? ? ?return spAttributeMapper.findByPage();
? }
}
@Mapper
public interface SpAttributeMapper {
? ?@Select(" select * from sp_attribute")
? ?List<SpAttribute> findByPage();
}
@Data
public class SpAttribute {
? ?private Integer attrId;
? ?private String attrName;
? ?private Integer catId;
? ?private String attrSel;
? ?private String attrWrite;
? ?private String attrVals;
? // getter&&setter
}
3.2 导入依赖
<pagehelper-version>1.4.1</pagehelper-version>
<dependency>
? ? <groupId>com.github.pagehelper</groupId>
? ? <artifactId>pagehelper-spring-boot-starter</artifactId>
? ? <version>${pagehelper-version}</version>
</dependency>
3.3 分页助手
?Page<SpAttribute> page1 = PageHelper.offsetPage(pageNum, pageSize);
?Page<SpAttribute> page2 = PageHelper.startPage(pageNum, pageSize);
3.4 测试:
// http://localhost:8088/findByPage1?pageNum=1&pageSize=10
? ?@GetMapping("/findByPage1")
? ?public Result<List<SpAttribute>> findByPage1(Integer pageNum, Integer pageSize) {
// ? ? PageHelper.offsetPage(pageNum, pageSize);
? ? ? ?PageHelper.startPage(pageNum, pageSize);
? ? ? ?return Result.ok(spAttributeService.findByPage());
? }
3.5 封装PageData
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageData<T> {
? ?private List<T> data;
? ?private long total;
}
3.6 测试
@GetMapping("/findByPage2")
? ?public Result<PageData<SpAttribute>> findByPage2(Integer pageNum, Integer pageSize) {
? ? ? ?Page<SpAttribute> result = PageHelper.startPage(pageNum, pageSize);
? ? ? ?spAttributeService.findByPage();
? ? ? ?PageData<SpAttribute> pageData = new PageData<>(result.getResult(), result.getTotal());
? ? ? ?return Result.ok(pageData);
}
3.7 分页演示
<!DOCTYPE html>
<html>
<head>
? ?<meta charset="utf-8">
? ?<title>table 模块快速使用</title>
? ?<!-- 引入 layui.css -->
? ?<link rel="stylesheet" href="//unpkg.com/layui@2.6.8/dist/css/layui.css">
?
? ?<!-- 引入 layui.js -->
? ?<script src="//unpkg.com/layui@2.6.8/dist/layui.js"></script>
</head>
<body>
<table id="demo" lay-filter="test"></table>
?
<script>
? ?layui.use('table', function () {
? ? ? ?var table = layui.table;
?
? ? ? ?//第一个实例
? ? ? ?table.render({
? ? ? ? ? ?elem: '#demo'
? ? ? ? ? , height: 312
? ? ? ? ? , url: '/findByPage3' //数据接口
? ? ? ? ? , page: true //开启分页
? ? ? ? ? , cols: [[ //表头
? ? ? ? ? ? ? {field: 'attrId', title: '主键id', width: 180, sort: true, fixed: 'left'}
? ? ? ? ? ? ? , {field: 'attrName', title: '属性名称', width: 180}
? ? ? ? ? ? ? , {field: 'catId', title: '类型id', width: 180, sort: true}
? ? ? ? ? ? ? , {field: 'attrSel', title: '动态参数', width: 180}
? ? ? ? ? ? ? , {field: 'attrWrite', title: '静态属性', width: 180}
? ? ? ? ? ]],
? ? ? ? ? ?parseData: function(d){
? ? ? ? ? ? ? ?return {
? ? ? ? ? ? ? ? ? ?code: d.code,
? ? ? ? ? ? ? ? ? ?msg: d.message,
? ? ? ? ? ? ? ? ? ?count: d.data.total,
? ? ? ? ? ? ? ? ? ?data: d.data.data,
? ? ? ? ? ? ? };
? ? ? ? ? }
? ? ? });
?
? });
</script>
</body>
</html>
@GetMapping("/findByPage3")
? public Result<PageData<SpAttribute>> findByPage3(@RequestParam(name = "page")
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?Integer pageNum,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @RequestParam(name = "limit")
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?Integer pageSize) {
? ? ? ?Page<SpAttribute> result = PageHelper.startPage(pageNum, pageSize);
? ? ? ?spAttributeService.findByPage();
? ? ? ?PageData<SpAttribute> pageData = new PageData<>(result.getResult(), result.getTotal());
? ? ? ?return Result.ok(pageData);
? }
3.8 增加排序功能
Page<SpAttribute> result = PageHelper.startPage(pageNum, pageSize, "attr_id DESC");
3.9 注意事项:
-
在使用排序时,我们发现pagehelper采用的是字符串拼接的形式,这样会有潜在的sql注入问题,这就要求我们在排序时必须严格校验排序的参数。必须与表或者实体类字段做好对应。
前端传参 ?
?@GetMapping("/findByPage")
? ?public Result<PageData<SpAttribute>> findByPage(@RequestParam(defaultValue = "1") Integer pageNum, ? ?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?@RequestParam(defaultValue = "10") Integer pageSize) {
? ? ?pageSize = pageSize >= 50 ? 50 : pageSize;
? ? ?return ...
? }
注意事项:
-
pageNum,pageSize指定默认值
-
pageNum 可以不做限制但是pageSize必做,防止前端恶意传参
pageHelper注意事项
-
分页插件是当调用startPage或者offsetPage之后开始拦截select语句,拦截之后,在select语句后拼接limit参数,拦截以后,分页功能关闭,再准备拦截下一次select语句。也就是说如果我们连续发了2个select语句请求只会拦截第一次操作。
-
不要在拦截select语句与请求的select语句之间使用page的任意方法,只有select语句请求以后,page里面才有数据。
4.0 事务
事务:transaction
4.1 基本使用
public class Account {
? ?private Integer id;
? ?private String name;
? ?private double balance;
? ?//getter&&setter
}
?@RestController
public class AccountController {
? ?@Autowired
? ?private AccountService accountService;
?
? ?@PostMapping("/balance")
? ?public void update() {
? ? ? ?boolean update = accountService.update();
? ? ? ?System.out.println(update);
? }
}
?
public interface AccountService {
? ?boolean update();
}
在你认为可能出现问题的方法上加上注解@Transactional
?@Service
public class AccountServiceImpl implements AccountService {
? ?@Autowired
? ?private AccountMapper accountMapper;
?
? ?@Transactional
? ?@Override
? ?public boolean update() {
? ? ? ?int sub = accountMapper.sub();
? ? ? ?int i = 1 / 0;
? ? ? ?int add = accountMapper.add();
? ? ? ?return sub == add ? true : false;
? }
}
@Mapper
public interface AccountMapper {
? ?int sub();
? ?int add();
}
<?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.whitecamellia.mapper.AccountMapper">
? ?<update id="add">
? ? ? UPDATE account
? ? ? set balance = balance + 500
? ? ? WHERE id = 2
? ?</update>
?
? ?<update id="sub">
? ? ? UPDATE account
? ? ? set balance = balance - 500
? ? ? WHERE id = 1
? ?</update>
?
</mapper>
4.2 rollbackFor
@Service
?
public class AccountServiceImpl implements AccountService {
? ?@Autowired
? ?private AccountMapper accountMapper;
?
? ?@Transactional ?// 默认 只有运行时异常 会自动回滚
? ?@Override
? ?public boolean update() throws FileNotFoundException {
? ? ? ?int sub = accountMapper.sub();
? ? ? // IOException 非运行时异常
? ? ? ?FileInputStream fileInputStream = new FileInputStream("C:\\WINDOWS\\1.txt");
? ? ? ?int add = accountMapper.add();
? ? ? ?return sub == add ? true : false;
? }
?
}
-
程序报错!尽管我们添加了事务注解,但是我们发现数据还是可以在数据库中进行修改
-
实际上spring框架在封装 @Transactional注解默认只会处理runtimeException,对于编译异常,默认交给用户处理。
-
我们希望编译异常也交给spring框架来管理,具体配置如下
@Transactional(rollbackFor = RuntimeException.class)//默认
@Transactional(rollbackFor = Exception.class) // 所有 遇见运行时异常 或者 非运行时异常。都会回滚,前提是该方法内的异常抛出而非捕获
@Service
public class AccountServiceImpl implements AccountService {
? ?@Autowired
? ?private AccountMapper accountMapper;
?
? ?@Transactional(rollbackFor = Exception.class)
? ?@Override
? ?public boolean update() throws FileNotFoundException {
? ? ? ?int sub = accountMapper.sub();
// ? ? int i = 1 / 0;
? ? ? ?FileInputStream fileInputStream = new FileInputStream("C:\\WINDOWS\\1.txt");
? ? ? ?int add = accountMapper.add();
? ? ? ?return sub == add ? true : false;
? }
?
}
4.3 事务的传播机制
-
REQUIRED (默认)
-
支持当前事务,如果当前没有事务,则新建事务
-
如果当前存在事务,则加入当前事务,合并成一个事务
-
-
REQUIRES_NEW
-
新建事务,如果当前存在事务,则把当前事务挂起(无论当前环境是否有事物)
-
这个方法会独立提交事务,不受调用者的事务影响,父级异常,它也是正常提交
-
-
NESTED
-
如果当前存在事务,它将会成为父级事务的一个子事务,方法结束后并没有提交,只有等父事务结束才提交
-
如果当前没有事务,则新建事务
-
如果它异常,父级可以捕获它的异常而不进行回滚,正常提交
-
但如果父级异常,它必然回滚,这就是和
REQUIRES_NEW
的区别
-
-
SUPPORTS
-
如果当前存在事务,则加入事务
-
如果当前不存在事务,则以非事务方式运行,这个和不写没区别
-
-
NOT_SUPPORTED
-
以非事务方式运行
-
如果当前存在事务,则把当前事务挂起
-
-
MANDATORY
-
如果当前存在事务,则运行在当前事务中
-
如果当前无事务,则抛出异常,也即父级方法必须有事务
-
-
NEVER
-
以非事务方式运行,如果当前存在事务,则抛出异常,即父级方法必须无事务
-
一般用得比较多的是
REQUIRED
,REQUIRES_NEW
;
REQUIRES_NEW
一般用在子方法需要单独事务。
@Transactional(propagation = Propagation.REQUIRED)
代码演示:
testService
public interface TestService {
? ?void TestInsert ();
}
testServiceImpl
@Service
public class TestServiceImpl implements TestService {
? ?@Autowired
? ?private PersonMapper personMapper;
?
? ?@Transactional
? ?@Override
? ?public void TestInsert() {
? ? ? ?Person person = new Person();
? ? ? ?person.setName("测试");
? ? ? ?person.setAge(20);
? ? ? ?// int i = 1 / 0;
? ? ? ?personMapper.insert(person);
? }
}
PersonServiceImpl
@Service
public class PersonServiceImpl implements PersonService {
? ?@Autowired
? ?private PersonMapper personMapper;
?
? ?@Autowired
? ?private TestService testService;
?
? ?@Override
? ?@Transactional(rollbackFor = Exception.class)
? ?public int insert(Person person) throws FileNotFoundException {
? ? ? ? ? int insert = personMapper.insert(person);
?
? ? ? ?testService.TestInsert();
// ? ? ? int i = 1 / 0;
// ? ? ? new FileInputStream("c:/1.txt");
? ? ? ?return insert;
? }
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
@Service
public class TestServiceImpl implements TestService {
? ?@Autowired
? ?private PersonMapper personMapper;
? ?// 默认
// @Transactional(propagation = Propagation.REQUIRED)
? ?@Transactional(propagation = Propagation.REQUIRES_NEW)
? ?@Override
? ?public void TestInsert() {
? ? ? ?Person person = new Person();
? ? ? ?person.setName("测试");
? ? ? ?person.setAge(20);
? ? ? ?personMapper.insert(person);
? }
}
4.4 事务的失效场景
5.0 Lombok
5.1 常用注解
@Getter
@Setter
@ToString
@EqualsAndHashCode
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
// ? @Getter
? ?private Integer id;
// ? @Setter
? ?private String name;
? ?private Integer age;
}
5.2 @Slf4j (日志框架)
编译前
@Slf4j
class Demo {
? ?public static void main(String[] args) {
?
? }
}
编译后
class Demo {
? ?private static final Logger log = LoggerFactory.getLogger(Demo.class);
?
? ?Demo() {
? }
?
? ?public static void main(String[] args) {
? ? ? // 日志输出级别:debug、info、warn、error
log.debug();
? }
}
5.3 级别演示
@Slf4j
@Service
public class PersonServiceImpl implements PersonService {
? ?@Autowired
? ?private PersonMapper personMapper;
? ?@Override
? ?public List<Person> findList() {
? ? ? ?log.debug("开始打印数据");
? ? ? ?return personMapper.findList();
? }
}
注意:我们项目配置文件中配置日志的打印级别是debug,故会将所有的日志全部打印
#开发环境日志级别
logging.level.com.whitecamellia.ssm_test=debug
#生产环境日志级别
#logging.level.com.whitecamellia.ssm_test=info
#再次精简打印信息
logging.level.root=warn
5.4 调试打印
@Slf4j
@Service
public class PersonServiceImpl implements PersonService {
? ?@Autowired
? ?private PersonMapper personMapper;
? ?@Override
? ?public List<Person> findList() {
? ? ? ?List<Person> list = personMapper.findList();
? ? ? ?log.debug("开始打印{}集合数据",list);
? ? ? ?return ?list;
? }
}
5.5 写入日志
#生产环境,将打印信息写到文件中
logging.file.name = logger.log
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!