初遇SpringBoot

直接用spring initializr初始化,https://start.aliyun.com/ ,添加springboot

semantic-ui

xadmin http://x.xuebingsi.com/

初始化

依赖

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>2.3.1.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>com.github.binarywang</groupId>
        <artifactId>weixin-java-mp</artifactId>
        <version>4.4.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <version>2.2.13.RELEASE</version>
        <scope>test</scope>
    </dependency>
</dependencies>

启动类

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

file Encoding

创建

打包

运行

java -jar ./hello.jar

更改配置

端口号

resources/application.properties

server.port=8081

resources/application.yml


server:
    port: 8084

springboot banner在线生成

https://www.bootschool.net/ascii/

resources/banner.txt

pom

不需要版本

debug

生效的自动配置类

debug:

true

模板引擎

spring.thymeleaf.cache=false

项目目录

server.servlet.context-path=/ming

日期格式

spring.mvc.date-format=yyyy-MM-dd

配置加载顺序

  1. bootstrap.propertes
  2. application.properties
    1. spring.profiles.active=dev
    2. application-dev.properties

底层

启动类下的所有资源被导入

启动器

注解

@SpringBootConfiguration: springboot的配置
    @Configuration: srping配置类
    @Component: spring组件
@EnableAutoConfiguration: 自动配置
    @AutoConfigurationPackage: 自动配置包
    @Import(AutoConfigurationPackages.Registrar.class)   自动配置包注册
    @Import(AutoConfigurationImportSelector.class) 自动配置导入选择
// 获取所有的配置
List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);

结论: springboot所有自动配置都是在启动的时候扫描并加载: spring. factories所有的自动配置类都在这里面,但是不一定生效,要判断条件是否成立,只要导入了对应的 start,就有对应的启动器了,有了启动器,我们自动装配就会生效,然后就配置成功!

xxxAutoConfigurartion 自动配置类: 给容器中添加组件

xxxProperties: 封装配置文件中的相关属性

配置

全局配置文件

  • application.properties

  • application.yum 空格严格

    • key: value 注意空格
student: 
  id: 1
  age: 13
student: {name: ming, age: 12}
arr: 
  - cat
  - dog
arr: [cat,dog]

注入属性值

@Value(“”) @Autowired

yaml

@ConfigurationProperties(prefix = “person”)

// 需要有有参无参
@Component
@ConfigurationProperties(prefix = "person")
public class Person {
    private Integer id;
    private String name;
    private List list;
    private Map<String, Object> map;
    private Date date;
    private Dog dog;
}

application.yaml

person:
  id: 1
  name: mingyue
  list:
    - fang
    - ming
    - yue
  map: {name: ming,age: 12}
  date: 2020/07/31
  dog:
    id: 1
    name: dahuang
 @Autowired
    public Person person;
使用spel表达式

${random.int}

${random.uuid}

${person.name:hello}

person:
  id: ${random.int}
  name: mingyue${random.uuid}
  list:
    - fang
    - ming
    - yue
  map: {name: ming,age: 12}
  date: 2020/07/31
  dog:
    id: 1
    name: dahuang${person.name:hello}

application.properties

utf-8

@PropertySource(value = "classpath:ming.properties")
pulbic class Person {
    @Value("${name}")
    private String name;
}

选择配置文件

有配置文件:application.properties、application-dev.propterties、application-test.propterties

application.properties进行配置:

spring.profiles.active=dev

使用yaml

server:
  port: 8081
spring:
  profiles:
    active: dev

---
server:
  port: 8082
spring:
  profiles: dev

注解

validte

依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
@Validated
public class Person {
    @Email(message="邮箱格式错误")
    private String email;
}

在Springboot启动器的web包下包含了javax.validation.Valid所以无需添加多余的依赖

package com.ming.ucenterservice.entity.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Validated
public class RegisterVo {
    @NotNull(message = "昵称不能为空")
    @ApiModelProperty(value = "昵称")
    private String nickname;

    @NotNull(message = "手机号不能为空")
    @Pattern(regexp  = "^\\d{11}$", message = "手机号格式不正确")
    @ApiModelProperty(value = "手机号")
    private String mobile;

    @NotNull(message = "密码不能为空")
    @ApiModelProperty(value = "密码")
    private String password;

    @NotNull(message = "验证码不能为空")
    @ApiModelProperty(value = "验证码")
    private String code;
}

controller

@PostMapping("/action/register")
    public Result registerByForm(@Valid @RequestBody RegisterUser registerUser){
        return userService.register(registerUser);
    }

捕获

@Slf4j
@ControllerAdvice
public class GlobalExceptionHandler {

    private final static String EXCEPTION_MSG_KEY = "Exception message : ";

    @ResponseBody
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result handleValidException(MethodArgumentNotValidException e){
                //日志记录错误信息
                log.error(Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage());
                //将错误信息返回给前台
                return Result.error(103, Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage());
    }

    // 验证异常
    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public R error(MethodArgumentNotValidException e) {
        return R.error()
                .code(ResultCode.ERROR)
                .message(Objects.requireNonNull(e.getBindingResult().getFieldError()).getDefaultMessage());
    }
}

相关注解

@Null 限制只能为null
@NotNull 限制必须不为null
@AssertFalse 限制必须为false
@AssertTrue 限制必须为true
@DecimalMax(value) 限制必须为一个不大于指定值的数字
@DecimalMin(value) 限制必须为一个不小于指定值的数字
@Digits(integer,fraction) 限制必须为一个小数,且整数部分的位数不能超过integer,小数部分的位数不能超过fraction
@Future 限制必须是一个将来的日期
@Max(value) 限制必须为一个不大于指定值的数字
@Min(value) 限制必须为一个不小于指定值的数字
@Past 限制必须是一个过去的日期
@Pattern(regexp = "") 限制必须符合指定的正则表达式
@Size(max,min) 限制字符长度必须在min到max之间
@Past 验证注解的元素值(日期类型)比当前时间早
@NotEmpty 验证注解的元素值不为null且不为空(字符串长度不为0、集合大小不为0@NotBlank 验证注解的元素值不为空(不为null、去除首位空格后长度为0),不同于@NotEmpty@NotBlank只应用于字符串且在比较时会去除字符串的空格
@Email 验证注解的元素值是Email,也可以通过正则表达式和flag指定自定义的email格式

EnableWebMvc

导入一个类

Configuration

配置类

Jackson

@ResponseBody            Controller 层 方法上标注 表示将返回值类型转为 Json 数据类型

@JsonIgnore                Entity 实体类 属性上标注 表示忽略 (此属性不做Json转化)

@JsonProperty("xxx")                Entity 实体类 属性上标注 表示起别名

@JsonFormat(pattern = "yyyy-MM-dd")    Entity 实体类 属性上标注 表示日期格式化

starter

启动器:

https://docs.spring.io/spring-boot/docs/2.0.3.RELEASE/reference/htmlsingle/#using-boot-starter

https://docs.spring.io/spring-boot/docs/2.3.2.RELEASE/reference/htmlsingle/#using-boot-starter

读取配置属性

ConfigurationProperties

全部自动注入

@ConfigurationProperties(prefix = "url")    
public class  ManyEnvProperties{  
   private String lm;  
   // 省列getter setter 方法 
}  

value(“${data.name}”)

一个个注入

@Component  
public class ManyEnvProperties {  
   @Value("${url.lm}")  
   private String lmPage;  

   /* 默认是 10000 */
   @Value("${fs.context.final.tenantId:10000}")
   private String tenantId;
}  

Environment

显示注入, 其次是在需要的地方获取值

@Autowired  
private Environment env;  
logger.info("===============》 " + env.getProperty("url.lm"));  

自己的properties

@Component 
@ConfigurationProperties(prefix = "url")  
@PropertySource("classpath:/myProperties.properties")  
public class PropertiesEnv {  
   private String lm;  
   // 省列getter setter 方法  
} 

导入配置类

@EnableConfigurationProperties(ThreadPoolConfigProperties.class)

public ThreadPoolExecutor threadPoolExecutor(ThreadPoolConfigProperties properties) {

}

配置常量

初始化bean可以通过实现InitializatizingBean接口或者@Configuration、@Bean(initMethod=”init”)指定初始化方法

ctrl + shift + u变大写

@Component
//@PropertySource("classpath:application.properties")
public class ConstantPropertiesUtil implements InitializingBean {

    @Value("${aliyun.oss.file.endpoint}")
    private String endpoint;

    @Value("${aliyun.oss.file.keyid}")
    private String keyId;

    @Value("${aliyun.oss.file.keysecret}")
    private String keySecret;

    @Value("${aliyun.oss.file.bucketname}")
    private String bucketName;

    public static String END_POINT;
    public static String ACCESS_KEY_ID;
    public static String ACCESS_KEY_SECRET;
    public static String BUCKET_NAME;

    @Override
    public void afterPropertiesSet() throws Exception {
        END_POINT = endpoint;
        ACCESS_KEY_ID = keyId;
        ACCESS_KEY_SECRET = keySecret;
        BUCKET_NAME = bucketName;
    }
}

乱码

server.tomcat.uri-encoding=UTF-8
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true
spring.messages.encoding=UTF-8

重新新建,重启即可

配置文件

Initializing Bean接口为bean提供了属性初始化后的处理方法,它只包括 afterPropertiesSet方法,凡是继承该接口的类,在bean的属性初始化后都会执行该方法。

生成配置类

package com.ming.oss.utils;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * 常量类,读取配置文件application.properties中的配置
 */
@Component
//@PropertySource("classpath:application.properties")

public class ConstantPropertiesUtil implements InitializingBean {
    @Value("${aliyun.oss.file.endpoint}")
    private String endpoint;

    public static String END_POINT;

    @Override
    public void afterPropertiesSet() throws Exception {
        END_POINT = endpoint;
    }
}

文件上传

// MultipartFile即可接收
file.getInputStream();
file.getOriginalFilename();

字符串模板

vue: {{ message }}  
mybatis: #{} ${} 
thymeleaf: ${}   [[ ${} ]]
php: {$name}
vue-value: /:id
js: `${}`
springboot-$Value: {$ming.url}

bean注入容器

  • @Configuration + @Bean
  • @ComponentScan + @Component
  • @Import 配合接口进行导入
    • 直接导入类
    • 导入实现importSelector接口的类
    • 其他
  • 使用FactoryBean,从多个中选出想要的
  • 实现BeanDefinitionRegistryPostProcessor进行后置处理

@Configuration

@Bean

@Configuration
public class MyConfiguration {
    @Bean(initMethod="init", destoryMethod="destory")
    public Person person() {
        Person person = new Person();
        person.setName("spring");
        return person;
    }
}

@ImportResource

@Configuration
@ImportResource(locations = {"classpath:bean/beans.xml"})

bean.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns="http://www.springframework.org/schema/beans"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="beanName"
          class="xxx.xxx.xxx">
        <property name="path" value="classpath:fileformat/"/>
        <property name="type" value=""/>
        <property name="version" value=""/>
    </bean>
</beans>

@Componet + @ComponentScan

@Component
public class Person {
    private String name;
}

@ComponentScan(basePackages = "com.springboot.initbean.*")
public class Demo1 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Demo1.class);
        Person bean = applicationContext.getBean(Person.class);
        System.out.println(bean);
    }
}

@Import

可以与@Configuration使用,导入配置类

直接导入

注解定义

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

    /**
   * 用于导入一个class文件
     * {@link Configuration @Configuration}, {@link ImportSelector},
     * {@link ImportBeanDefinitionRegistrar}, or regular component classes to import.
     */
    Class<?>[] value();
}

使用


/**
* 直接使用@Import导入person类,然后尝试从applicationContext中取,成功拿到
**/
@Import(Person.class)
public class Demo1 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Demo1.class);
        Person bean = applicationContext.getBean(Person.class);
        System.out.println(bean);
    }
}

ImportSelector

@import引入实现了importselector的接口(将类包路径字符串数组返回),包名数组所有都加载进来,都能使用

class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        return new String[]{"com.springboot.pojo.Person"};
    }
}

@Import(MyImportSelector.class)
public class Demo1 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Demo1.class);
        Person bean = applicationContext.getBean(Person.class);
        System.out.println(bean);
    }
}

ImportBeanDefinitionRegistrar

@Import(MyImportBeanDefinitionRegistrar.class)
public class Demo1 {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Demo1.class);
        Person bean = applicationContext.getBean(Person.class);
        System.out.println(bean);
    }
}

class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 构建一个beanDefinition, 可以简单理解为bean的定义
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Person.class).getBeanDefinition();
        // 将beanDefinition注册到Ioc容器中
        registry.registerBeanDefinition("person", beanDefinition);
    }
}

DeferredImportSelector

和实现importSelector接口一样,DeferredImportSelector extends importSelector,spring的处理方式不同,与springboot自动导入配置文件、延迟导入有关,很重要

@Import(MyDeferredImportSelector.class)
public class Demo1 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Demo1.class);
        Person bean = applicationContext.getBean(Person.class);
        System.out.println(bean);
    }
}
class MyDeferredImportSelector implements DeferredImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        // 也是直接将Person的全限定名放进去
        return new String[]{Person.class.getName()};
    }
}

实现FactoryBean

它是IOC容器的顶级接口,需要把该工厂类放到容器管理,就会自动载入具体的类

@Configuration
public class Demo1 {
    @Bean
    public PersonFactoryBean personFactoryBean() {
        return new PersonFactoryBean();
    }
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(Demo1.class);
        Person bean = applicationContext.getBean(Person.class);
        System.out.println(bean);
    }
}

class PersonFactoryBean implements FactoryBean<Person> {

    /**
     *  直接new出来Person进行返回,一般会进行判断,决定返回谁
     */
    @Override
    public Person getObject() throws Exception {
        return new Person();
    }
    /**
     *  指定返回bean的类型.
     */
    @Override
    public Class<?> getObjectType() {
        return Person.class;
    }
}

BeanDefinitionRegistryPostProcessor

利用到了 BeanDefinitionRegistry,spring容器启动的时候会执行他的方法

等beanDefinition加载完毕后,进行后置处理,调整IOC容器中的beanDefintion,将影响到初始化Bean

public class Demo1 {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        MyBeanDefinitionRegistryPostProcessor beanDefinitionRegistryPostProcessor = new MyBeanDefinitionRegistryPostProcessor();
        applicationContext.addBeanFactoryPostProcessor(beanDefinitionRegistryPostProcessor);
        applicationContext.refresh();
        Person bean = applicationContext.getBean(Person.class);
        System.out.println(bean);
    }
}

class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Person.class).getBeanDefinition();
        registry.registerBeanDefinition("person", beanDefinition);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }
}

启动执行

CommandLineRunner和ApplicationRunner

@Component
public class JDDRunner implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
        System.out.println(args);
        System.out.println("这个是测试ApplicationRunner接口");
    }
}

Spring类

ApplicationContext

ApplicationContext是IOC容器,顶层接口是BeanFactory(能够通过该工厂bean获取具体的类)

ApplicationContextAware

在某些特殊的情况下,Bean需要实现某个功能,但该功能必须借助于Spring容器才能实现,此时就必须让该Bean先获取Spring容器,然后借助于Spring容器实现该功能。

为了让Bean获取它所在的Spring容器,可以让该Bean实现ApplicationContextAware接口。ApplicationContextAware ,setApplicationContext方法会自动传入把上下文环境对象。在ApplicationContextAware的实现类中,就可以通过这个上下文环境对象得到Spring容器中的Bean。

看到—Aware就知道是干什么的了,就是属性注入的,但是这个ApplicationContextAware的不同地方在于,实现了这个接口的bean,当spring容器初始化的时候,会自动的将ApplicationContext注入进来。

spring拿IOC容器,ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、AnnotationConfigApplicationContext

SpringBoot中,因为没有了ioc配置文件,全都成自动化的了,无法通过上述方式拿到ApplicationContext对象

有的需求是必须要通过Spring容器对象才能实现的

例如需要将所有三方渠道的代理类加载到ioc容器,然后在代码执行过程中用ioc容器对象的getBean()动态获取某一个三方渠道的代理类对象并执行对应的方法

ApplicationContextAware接口是用来获取框架自动初始化的ioc容器对象的。

package learn.utils;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class SpringContextUtil implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext context) throws BeansException {
        applicationContext = context;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
    //获取Bean
    public static <T> T getBean(Class<T> requiredType){
        return getApplicationContext().getBean(requiredType);
    }
    public static <T> T getBean(String name){
        return (T) getApplicationContext().getBean(name);
    }
}

使用

SpringContextUtil.getBean(UserAccountService.class);
// 可以在类中调用本类的方法,使该被调用方法的注解生效,如@Transactional

AutowireCapableBeanFactory

在BeanFactory的基础上实现对已存在实例的管理。

可以使用这个接口集成其他框架,捆绑并填充并不由Spring管理生命周期并已存在的实例。

获取接口的实例,ApplicationContext的getAutowireCapableBeanFactory方法

装配策略:

  • 不自动注入:AUTOWIRE_NO
  • 使用BeanName策略注入:AUTOWIRE_BY_NAME
  • 使用类型装配策略:AUTOWIRE_BY_TYPE
  • 使用构造器装配策略:AUTOWIRE_CONSTRUCTOR
  • 自动装配策略:AUTOWIRE_AUTODETECT

ApplicationContext.getAutowireCapableBeanFactory().autowireBean(bean)

某对象可能是直接new的对象,不是直接从bean获取的,所以其@Autowire等方法无法注入属性,可以通过以上方法

能通过反射获取到我们该对象的属性,若是注解了Autowired、Value、Inject时,进行Bean组装

如Filter没交给spring管理,但是想注入spring属性,https://www.jianshu.com/p/14dd69b5c516

又如Quartz Job的创建没交给Spring管理

事务

分为编程式事务和声明式事务

  • Spring提供的最原始的事务管理方式是基于TransactionDefinitionPlatformTransactionManagerTransactionStatus 编程式事务。
  • TransactionTemplate的编程式事务管理是使用模板方法设计模式对原始事务管理方式的封装

TransactionManager

  1. PlatformTransactionManager :用于命令式事务管理的事务管理器,定义了获取事务,回滚事务和提交事务的规范
  2. ReactiveTransactionManager :用于响应式事务管理的事务管理器

TransactionException:事务异常,Spring家定义的事务超类,我们自定义事务异常时可以继承TransactionException

TransactionStatus :getTransaction方法的返回值;TransactionStatus 可能表示新事务或可以表示现有事务

TransactionDefinition :标准事务概念,包含了事务的传播行为,隔离级别,超时设置,是否只读状态等信息

依赖

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.2.0</version>
</dependency>

包含了spring-boot-starter-jdbc的依赖,spring-boot-starter-jdbc中包含DataSourceTransactionManager事务管理器以及自动注入配置类DataSourceTransactionManagerAutoConfiguration。

简介

声明式事务

配置类中@EnableTransactionManagement(默认有) + 方法@Transactional

@Transactional(rollbackFor = Exception.class)

优点:

  • 代码侵入小

缺点:

  • 只能在public方法上
  • 无法自调本类加了@Transactional的方法

编程式事务

  1. 通过TransactionTemplate(命令式)或者TransactionalOperator(响应式)
  2. 直接通过TransactionManager来实现
transactionTemplate

重点关注业务代码逻辑,更方便,更安全

/** 1、函数式编程 */
@Service
public class TestServiceImpl {
    @Resource
    private TransactionTemplate transactionTemplate;

    public Object testTransaction() {
        //数据库查询
        dao.select(1);
        return transactionTemplate.execute(status -> {
            //数据库新增
            dao.insert(2);
            dao.insert(3);
            return new Object();
        });
    }
}

/** 2、有返回值的TransactionCallback */
public class SimpleService implements Service {

    private final TransactionTemplate transactionTemplate;

    public SimpleService(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);
    }

    public Object someServiceMethod() {
        return transactionTemplate.execute(new TransactionCallback() {
            public Object doInTransaction(TransactionStatus status) {
                updateOperation1();
                return resultOfUpdateOperation2();
            }
        });
    }
}

/** 3、无返回值的TransactionCallbackWithoutResult */
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
    protected void doInTransactionWithoutResult(TransactionStatus status) {
        updateOperation1();
        updateOperation2();
    }
});
platformTransactionManager

容易漏掉commit和rollback

// 加事务
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = platformTransactionManager.getTransaction(def);
try {
    platformTransactionManager.rollback(status);
    platformTransactionManager.commit(status);
} catch (Exception e) {
    if (!status.isCompleted()) {
        platformTransactionManager.rollback(status);
    }
    throw e;
}

配置类

@Configuration
public class TransactionManagerConfig implements TransactionManagementConfigurer {

    /**
     * The Data source.
     */
    @Resource
    private DataSource dataSource;

    /**
     * The Data source transaction manager.
     */
    @Resource
    private DataSourceTransactionManager dataSourceTransactionManager;

    /**
     * Data source transaction manager data source transaction manager.
     *
     * @return the data source transaction manager
     * @date
     */
    @Primary
    @Bean(name = "defaultTransactionManager")
    public DataSourceTransactionManager dataSourceTransactionManager() {
        return new DataSourceTransactionManager(dataSource);
    }

    @Override
    public PlatformTransactionManager annotationDrivenTransactionManager() {
        return dataSourceTransactionManager;
    }
}

传播机制

propagation

  • (默认)REQUIRED :如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  • SUPPORTS :如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
  • MANDATORY :如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  • REQUIRES_NEW :创建一个新的事务,如果当前存在事务,则把当前事务挂起。
    • 应用场景粗日志用于错误日志记录,如果外层方法报错了,也可以将错误信息插入到数据库
  • NOT_SUPPORTED :以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • NEVER :以非事务方式运行,如果当前存在事务,则抛出异常。
  • NESTED :如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 REQUIRED

@Transactional(propagation = Propagation.REQUIRED)

隔离级别

isolation

public enum Isolation {
    DEFAULT(TransactionDefinition.ISOLATION_DEFAULT),
    READ_UNCOMMITTED(TransactionDefinition.ISOLATION_READ_UNCOMMITTED),
    READ_COMMITTED(TransactionDefinition.ISOLATION_READ_COMMITTED),
    REPEATABLE_READ(TransactionDefinition.ISOLATION_REPEATABLE_READ),
    SERIALIZABLE(TransactionDefinition.ISOLATION_SERIALIZABLE);
}
  • DEFAULT :默认值,表示使用底层数据库的默认隔离级别。大部分数据库为READ_COMMITTED(MySql默认隔离级别为REPEATABLE)
  • READ_UNCOMMITTED :该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读和不可重复读,因此很少使用该隔离级别。
  • READ_COMMITTED :该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
  • REPEATABLE_READ :该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略。该级别可以防止脏读和不可重复读。
  • SERIALIZABLE :所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

通过使用 isolation 属性设置,例如:@Transactional(isolation = Isolation.DEFAULT)

web开发

自动装配

springboot到底帮我们配置了什么?我们能不能进行修改?能修改哪些东西?能不能扩展?

  • xxxxAutoConfiguration 向容器中自动配置组件
  • xxxxProperties 自动配置类,装配配置文件中自定义的一些内容

初见

问题

  • 导入静态资源
  • 首页404
  • jsp,使用模板引擎Thymeleaf
  • 装配扩展SpringMVC
  • curd
  • 拦截器
  • 国际化

webjars

meaven引入jquery、bootstrap等

http://localhost:8080/webjars/..

静态资源

localhost:8080/..

优先级

resource/public

private static final String[] CLASSPATH_RESOURCE_LOCATIONS = new String[]{"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"};

templates目录下的页面,只能通过controller跳转(需要模板引擎)

2.1.7

/static

#关闭默认图标
spring.mvc.favicon.enabled=fasle

扩展MVC

@Configuration

thymeleaf

https://www.thymeleaf.org/

关闭缓存,不然每次都要重启

spring.thymeleaf.cache=false

使用

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

html页面放入templates下

th:text

th:style

@GetMapping("/thymeleaf")
public String thymeleaf(Model model) {
    model.addAttribute("msg", "hello thymeleaf");
    model.addAttribute("list", Arrays.asList("myname", "mingyue", "fusu"));
    return "thymeleaf";
}
<div th:text="${msg}"></div>
<div th:utext="${msg}"></div> 可以识别标签
<div th:each="item : ${list}" th:text="${item}"></div>
<div th:each="item : ${list}">[[ ${item} ]]</div>

语法

简单表达:

  • 变量表达式: ${...}
  • 选择变量表达式: *{...}
  • 消息表达式: #{...}
  • 链接网址表达式: @{...}
  • 片段表达式: ~{...}

字面

  • 文本文字:'one text''Another one!',…
  • 号码文字:0343.012.3,…
  • 布尔文字:truefalse
  • 空字面: null
  • 文字标记:onesometextmain,…

文字操作:

  • 字符串连接: +
  • 文字替换: |The name is ${name}|

算术运算:

  • 二元运算符:+-*/%
  • 减号(一元运算符): -

布尔运算:

  • 二元运算符:andor
  • 布尔否定(一元运算符): !not

比较和平等:

  • 比较:><>=<=gtltgele
  • 平等运营商:==!=eqne

有条件的运营商:

  • IF-THEN: (if) ? (then)
  • IF-THEN-ELSE: (if) ? (then) : (else)
  • 默认: (value) ?: (defaultvalue)

特殊代币:

  • 无操作: _

标签

th:placeholder=”#{login.username}”
th:text=”#{login.btn}”
th:href=”@{/index.html(l=’zh_CN’)}”
th:if=”{not #strings.isEmpty(msg)}”
th:fragment=”topbar”
th:insert=”{dashboard::topbar}”
th:replace=”
{commons/commons::topbar}” ()传递参数
th:href=”@{/admin/people/(${people.id})}” 使用restful风格传递参数
接受@GetMapping(“/admin/people/${id}”)
(@PathVariable(“id”)Interger id)

  • th:include 将目标片段中的HTML加入当前元素
  • th:replace 将目标片段替换自身
  • th:insert 将目标片段整个加入当前元素

传递参数进入(添加活跃class)

<div th:insert="~{common/commons::sideBar(active='list.html')}"></div>

接受

<a th:class="${active=='list.html'?'nav-link active':'nav-link'}" th:href="@{/admin/list}"></a>

函数

`$