SSM总结
一.框架是什么?
一堆jar包和配置文件,对其他工具进行包装集成更好用
库补充:日志和lombok
日志:
种类:
1.统一的日志接口SLF4J,实现接口的日志有Log4j、Logback
2.java自带日志框架JUL
级别:
- TRACE: 细粒度的信息,通常用于调试。
- DEBUG: 调试信息,帮助开发者理解程序运行状态。
- INFO: 重要的运行信息,表明程序的正常运行状态。
- WARN: 警告信息,表明可能的问题。
- ERROR: 错误信息,指示程序中发生了异常。
- FATAL: 严重错误,程序可能无法继续运行。
其中Logback是以后springboot默认的,学下这个。
1.导包 logback-classic (其他包是不全的)
2.配置文件 logback.xml
配置在控制台输出格式,配置文件输出格式,配置默认级别以及输出到哪里
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!-- 控制台输出 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<FileNamePattern>${LOG_HOME}/TestWeb.log.%d{yyyy-MM-dd}.log</FileNamePattern>
<!--日志文件保留天数-->
<MaxHistory>30</MaxHistory>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
</encoder>
<!--日志文件最大的大小-->
<triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
<MaxFileSize>100MB</MaxFileSize>
</triggeringPolicy>
</appender>
<!--mybatis log configure-->
<logger name="com.apache.ibatis" level="TRACE"/>
<logger name="java.sql.Connection" level="DEBUG"/>
<logger name="java.sql.Statement" level="DEBUG"/>
<logger name="java.sql.PreparedStatement" level="DEBUG"/>
<!-- 日志输出级别,logback日志级别包括五个:TRACE < DEBUG < INFO < WARN < ERROR -->
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
3.在 Java 代码中使用 Logback 记录日志
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyClass {
private static final Logger logger = LoggerFactory.getLogger(MyClass.class);
public void myMethod() {
logger.debug("This is a debug message");
logger.info("This is an info message");
logger.warn("This is a warning message");
logger.error("This is an error message");
}
}
lombok:
- @Slf4j
使用 SLF4J 作为日志记录框架。它会自动生成一个名为log
的日志记录器@Slf4j public class MyClass { public void myMethod() { log.debug("This is a debug message"); log.info("This is an info message"); log.warn("This is a warning message"); log.error("This is an error message"); } }
- @Data
包含@ToString
、@EqualsAndHashCode
、@Getter
、@Setter
和@RequiredArgsConstructor
- @NoArgsConstructor / @AllArgsConstructor
生成无参构造函数和全参构造函数
二.Mybatis框架
知识思维导图:ProcessOn Mindmap
1.其中CRUD->使用mybatis进行增删改查案例
<mapper namespace="com.yang.Mapper.studentMapper">
<select id="getAllStudent" resultType="com.yang.pojo.Student">
select * from student
</select>
<select id="getStudentById" resultType="com.yang.pojo.Student">
select * from student where id=#{id}
</select>
<insert id="addStudent">
insert into student (id,name,age) values (#{id},#{name},#{age})
</insert>
<update id="updateStudent">
update student set name=#{name},age=#{age} where id=#{id}
</update>
<delete id="deleteStudent" >
delete from student where id=#{id}
</delete>
</mapper>
SQL映射文件中的配置比较完整的写法是:
<select id="selectByName" resultType="student" parameterType="java.lang.String">
select * from t_student where name = #{name, javaType=String, jdbcType=VARCHAR}
</select>
其中sql语句中的javaType,jdbcType,以及select标签中的parameterType属性,都是用来帮助mybatis进行类型确定的。不过这些配置多数是可以省略的。因为mybatis它有强大的自动类型推断机制。
- javaType:可以省略
- jdbcType:可以省略
- parameterType:可以省略
2.其余没仔细写的可以看MyBatis · 语雀(动力节点的很不错,超详细)
原理
Mybatis获取连接数据库是对jdbc进行了封装
SqlSessionFactoryBuilder
1.会读核心配置文件xml得到当前环境:拿到xml文件中对应的事务管理工厂和数据源
(id TransactionFactory DataSource)
2.解析Mapper文件,获取所有的SQL映射对象Map
3.执行build方法创建SqlSessionFactory
(解读DefaultSqlSessionFactory类-openSessionFromDataSource)
1.得到SqlSessionFactoryBuilder解析完后的环境 (id TransactionFactory DataSource)
2.从环境中拿到TransactionFactory,传入 DataSource,得到一个事务管理器Transaction
3.得到执行器,包装事务管理器Transaction
4.执行openSeesion方法得到sqlSession
(解读DefaultSqlSessionFactory类-openSession)
1.openSession就是执行上述步骤得到sqlSession包装了执行器
并添加对应执行方法 执行语句从sql映射对象map中拿
2.sqlSession执行数据库操作时,会用事务管理器控制事务,从数据源得到一个数据库连接,然后用这个连接执行sql语句,异常时事务管理器会回滚事务,成功后提交事务,最后将连接归还给数据源。
总结:sqlSession->执行器->事务管理器->数据源
执行语句从sql映射对象map中拿
如果抽象成对象:
SqlSessionFactoryBuilder读配置文件得到DataSource和事务管理器-> SqlSessionFactory -> SqlSession
三.spring框架
1.spring分为八大模块
我们要学的:
Spring Core --> 实现了ioc管理bean,容器为BeanFactory
Spring AOP -->面向切面编程
Spring Context--> 构建于core封装包基础上的context封装包,提供了框架式的对象访问方法(如:容器ApplicationContext),集成了很多包(如:SpEL-->spring-expression)
Spring DAO-->简化JDBC,处理数据库相关,如处理事务和数据库异常
Spring ORM --> spring支持与ORM框架集成
Spring Web --> web技术 包装了servlet websocket
Spring MVC --> 全名 spring web MVC,包装了spring web,以MVC架构实现web项目
Spring Webflux --> ?
使用spring只需要导入相关依赖就可以了,spring一共就有20多个包
1.普通项目基础导包(ioc aop SpEL) --> spring-context
web项目基础导包(ioc aop SpEL web) --> spring-webmvc
(二选一)
2.在以上基础上使用其他模块内容
用Spring+AspectJ框架实现的aop,要导入spring-aspects
用Spring管理Mybatis要导入Mybatis写好的包,mybatis-spring
以及Sring提供整合的工具spring-jdbc(包含事务管理)
用Spring集合Junit框架,要导入spring-test
ps:spring-core是所有依赖的依赖,它依赖了commons-logging,所以spring在打开的时候会有日志输出。我们可以导入logback依赖,配置xml,显示的就是此日志输出了。
spring核心框架体系结构(各个jar包作用)_spring-instrument-tomcat-CSDN博客
2.spring-context(ioc)
实现过程
设计模式:ioc 控制反转思想
完成Spring对Bean的管理(Bean对象的创建以及Bean对象中属性的赋值)
DI(依赖注入) :ioc思想通过DI实现。
DI常见的实现方式包括两种:set注入 构造注入
实现控制反转,三个技术:解析配置文件 工厂模式 反射
1.解析xml配置文件或扫描注解
Spring会从上到下读xml文件,解析出每个bean的定义
如果发现某个bean的ref是另一个bean,会先将此bean解析
再接着解析其他bean
解析完创建BeanDefinition 是按解析顺序创建的
2.容器初始化【实例化bean】
容器有什么?
- BeanFactory:基础容器,用于管理简单的 Bean。
- ApplicationContext:功能更全的容器,提供国际化、事件传播、AOP 等功能。
容器有数据结构 Map<String,Object>中,key就是对象的id或name,value就是对象
容器有getBean方法 -- 工厂设计模式(通过容器工厂得到我们想要的类)
遍历BeanDefinition,创建bean实例,区分是单例还是原型模式
(这里还可以指定加载顺序,会先排队交换位置再加载 属性:depends-on )
[单例且懒加载为false]在容器启动时就会实例化 Bean,并将其存放在 Map
中。
[单例且懒加载为true]容器只在第一次调用 getBean
时创建 Bean,并将其存储以供后续调用使用。
[原型]每次调用 getBean
方法都会创建一个新的 Bean 实例,不会在容器中缓存。
[ps:在实例化的时候
1.没配工厂:会看有没有指定构造方法注入,没有的话会得到无参构造方法实例化对象
2.如果配置了factory:在实例化时,会去找工厂然后执行工厂方法,得到实例化对象]
总结:实例化bean用到的属性有(id name class [scope lazy-init depends-on factory-method factory-bean] )
3.依赖注入【属性赋值】:
- 构造注入(已经在实例化的时候注入了)
- set注入
遍历map中的bean
将property中的内容进行赋值
1.注入简单类型 vlaue (基本数据类型及包装类 String URI URL....)
[可用BeanUtils.isSimpleValueType()判断]
2.注入Bean ref
3.注入数组 array-ref/value
4.注入List list-ref/value
5.注入Set set-ref/value
6.注入Map map-<entry key/key-ref="1" value/value-ref="北京大兴区"/>
执行set方法(set方法是name第一个字母大写后拼接得到的)
也可以自动装配(autowire),也是基于set方法的。
分为byName何byType(前置一定要规范!属性名为aaa,那set方法应为setAaa)
byName-->根据属性名去配置文件找对应name或id的bean类,执行对应set方法
byType-->根据属性的类型,去配置文件中找对应类型的bean,执行对应set方法
如果有歧义,可以在加上autowire-candidate是否为候选人或者设置primary优先级
总结:属性赋值常用到的属性有(autowire autowire-candidate primary)
4.初始化bean:
之后调用初始化bean的方法 init-method
以上内容都是在new容器时就会进行的。所有的bean对象(作用域是单例的)会在new时就全部创建好放到容器中了,之后拿到的都是这个对象。
5.使用bean:
getBean会判断容器中有没有,有的话就直接返回,没有的话看是懒加载了还是原型。
如果是懒加载,创建对象-->依赖注入-->调用 init-method方法-->放入容器,下次拿直接拿容器对象
如果是原型,创建对象-->依赖注入-->调用 init-method方法-->new新对象返回,下次拿也是这几步
6.销毁bean:
在容器中的bean都会销毁,调用销毁方法destroy-method
(原型不交给容器管,所以没这步)
以上步骤就是按bean生命周期来讲解的。
由容器管理的单例bean,生命周期完整。
原型bean不由容器管理,生命周期只到使用bean。
使用方式
1.配置xml文件方式
将[配置名].xml放到resoures底下,spring也是从target下读文件的
配置文件常用内容:
bean中可以写:(beans上有些也可以配,配了全部bean都有此特性)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd" default-lazy-init="true">
<!--导入其他配置文件-->
<import resource="other.xml"/>
<!--注册bean对象-->
<bean name="Car" class="com.yang.pojo.Car"/>
<!--给此对象起别名-->
<alias name="Car" alias="smallCar"/>
<!--设置此对象的获得方式 1.singleton(默认) 此对象每次获取都是同一个对象
2.prototype 每此获取会new一个新对象-->
<bean class="com.yang.pojo.Customer" scope="prototype"/>
<!--工厂的方法得到对象,其中class是工厂类,得到的是car对象,yeah也是car对象的名字
没有实例化factory对象,所以method必须是static的-->
<bean name="yeah" class="com.yang.pojo.Factory" factory-method="getStaticCar"/>
<!--也可以实例化factory类 这里的method必须是非static的
factory还是实例化对象的名字-->
<bean name="factory" class="com.yang.pojo.Factory"/>
<bean factory-bean="factory" factory-method="getCar"/>
<!--注入-->
<bean name="小张" class="com.yang.pojo.Customer">
<constructor-arg index="0" value="小张"/>
<property name="id" value="2"/>
<!-- <constructor-arg name="id" value="5"/>-->
</bean>
</beans>
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
2.注解方式
1.解析配置文件,可以用java类替代配置文件@Configuration
注意配置扫包,扫哪些包上的注解@ComponentScan
在这里面也可以注册bean,用@Bean注解,这样可以弥补接下来的注解没有工厂模式的缺陷。
在这里获得的对象也是我们通过方法创建出来的可以加其他处理。
(@Bean里面可以配置名字,默认名是方法名,配置自动装配,初始化方法和摧毁方法)
ps:对于编写的类型,如果要注册为Bean,那么只能在配置类中完成
@Configuration
@ComponentScan({"com.yang.pojo","com.yang.dao"})
public class Config {
@Bean(name="customer")
public Customer customer() {
//工厂模式 可以加其他修饰
Customer customer = new Customer();
return customer;
}
//也可以自动装配,将装配对选以参数形式传来,会自动找注册过的bean
@Bean(initMethod = "function")
public Customer customer2(Car car) {
Customer customer = new Customer();
customer.setCar(car);
return new Customer();
}
}
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
2.对class上面加注解
声明bean的注解 :@Component @Controller @Service @Repository
这四个作用一样分包更清晰
@Component会看@Autowired是否加构造方法上了,有执行;没有执行无参构造方法,如果没有无参会执行有参的,前提是所有参数的类型都是可以在上下文中找到的bean。
并且会给bean起默认名字:Bean类名首字母小写
也可以自己起:@Component("myCar")
其他:
- scope-->@Scope("prototype")
- lazy-init-->@Lazy(true)
- depends-on-->@DependsOn("customer")
- factory-method-->用配置在配置类中替代了
3.对属性加注解
@Value 简单类型
自动装配:
- @Autowired注解默认根据类型装配byType,如果想根据名称装配,需要配合@Qualifier注解一起用。@Autowired并不是只能用于字段,对于构造方法(指定构造方法)或是Setter都可以。(如果自动装配产生冲突,优先@Component的)
- @Resource注解默认根据名称装配byName,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型byType装配。只能用字段和Setter。
3.spring-aspects(aop)
面向切面
切面就是与业务逻辑无关的代码单独提取出来形成一个独立切面(切点+通知)
1.确定切点(方法)
2.确定通知(增强方法)
3.确定通知位置(方法前后还是环绕)
Spring对AOP的实现包括以下3种方式:
- 第一种方式:Spring框架结合AspectJ框架实现的AOP,基于注解方式。
- 第二种方式:Spring框架结合AspectJ框架实现的AOP,基于XML方式。
- 第三种方式:Spring框架自己实现的AOP,基于XML配置方式。
注解方式:【记住这种】
(先再配置类上添加aop注解可用@EnableAspectJAutoProxy)
1.切面类@Aspect@Component
2.编写方法
3.指定其切入位置
- 前置通知:@Before 目标方法执行之前的通知
- 后置通知:@AfterReturning 目标方法执行之后的通知
- 环绕通知:@Around 目标方法之前添加通知,同时目标方法执行之后添加通知,需显示调用目标方法
- 异常通知:@AfterThrowing 发生异常之后执行的通知
- 最终通知:@After 放在finally语句块中的通知
可以用JoinPoint得到切点的具体信息,其中环绕通知还可以用ProceedingJoinPoint来获得切点具体信息。比如:获得目标方法的第一个参数joinPoint.getArgs()[0].toString();
我们还可以通过args(参数名)
进行参数绑定,直接获取参数(例子在下面)
环绕信息必须调用目标方法表示继续,如果有参数,也可以将新的参数传入目标方法。
4.里面填execution表达式指定位置
填写格式:修饰符 包名.类名.方法名称(方法参数)
如:@Around("execution(* com.powernode.spring6.service.OrderService.*(..))")
方法参数中填..代表参数什么类型都可以
也可以将这个切点放到一个方法上,大家都可以用,在任一类中写这个方法都可以,统一切点
@Pointcut("execution(* com.yang.pojo.*(..))")
public void pointcut() {}
(用aop的类,再getBean时,返回的是一个代理类,不是单纯的目标类
可以用getClass查看其类型,如:com.yang.pojo.Customer$$SpringCGLIB$$0)
举例:
@Aspect
@Component
public class MainAspects {
@Before(value = "execution(* com.yang.pojo.Customer.function(String))")
public void before(JoinPoint joinPoint) {
System.out.println(joinPoint.getArgs()[0]);
System.out.println("我是之前执行的内容!");
}
}
@Aspect
@Component
public class MainAspects {
@Before(value = "execution(* com.yang.pojo.Customer.function(String)) && args(str)",argNames ="str" )
public void before(String str) {
//命名绑定模式就是根据下面的方法参数列表进行匹配
//这里args指明参数,注意需要跟原方法保持一致,然后在argNames中指明
System.out.println(str);
System.out.println("我是之前执行的内容!");
}
}
@Aspect
@Component
public class MainAspects {
@Around(value = "execution(* com.yang.pojo.Customer.function(..))")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println(joinPoint.getArgs()[0].toString());
System.out.println("我是之前执行的内容!");
//必须手动调用方法
//Object proceed = joinPoint.proceed();
//还可以传入新的参数列表给目标方法
Object proceed = joinPoint.proceed(new String[]{
"1234"});
System.out.println("我是之后执行的内容!");
}
}
4.spring-jdbc 和 mybatis-spring(整合mybatis)
回顾:mybatis
SqlSessionFactoryBuilder读配置文件得到DataSource和事务管理器-> SqlSessionFactory -> SqlSession
现在这些对象全用sring来管理,在mybatis-spring依赖中,为我们提供了SqlSessionFactoryBean和SqlSessionTemplate可以注册相当于SqlSessionFactory和SqlSession
Spring:
DataSource和TransManager-->SqlSessionFactoryBean
(这里可以选择不配置 SqlSessionTemplate,因为它可以在需要时被自动创建)
SqlSessionFactoryBean在创建时除了可以传入数据源,还可以传入Mapper.xml的资源以及mybatis核心文件资源。(mybatis核心文件的enviroment等会被忽略)
之后写Mapper接口实现类用sqlSessionTemplate.getMapper拿到对应Mapper接口动态生成的对象,就可以用实现类自动装配了。
动态生成:
- mybatis通过Mapper接口的类名为namesapce,方法名为id找到对应方法然后实现方法。
- 如果是注解,直接拿到上面方法然后实现方法。
也可以直接用注解@MapperScan("com.yang.dao"),扫描包下所有接口,生成实现类放入Spring,直接getBean获取,Mapper接口直接可以用来自动装配。(本质仍是自动创建SqlSessionTemplate然后调用getMapper)
SqlSessionTemplate介绍
它其实就是官方封装的一个工具类,我们可以将其注册为Bean,这样我们随时都可以向IoC容器索要对象,而不用自己再去编写一个工具类了,我们可以直接在配置类中创建。其实现了SqlSession接口,且线程安全。使用了SqlSessionTemplate之后,我们不再需要通过SqlSessionFactory.openSession()方法来创建SqlSession实例;使用完成之后,也不要调用SqlSession.close()方法进行关闭。另外,对于事务,SqlSessionTemplate 将会保证使用的 SqlSession 是和当前 Spring 的事务相关的。
对于这种别人编写的类型,如果要注册为Bean,那么只能在配置类中完成。2.不保留mybatis核心配置文件
配置方式
我们只需配数据源bean就可以正常使用了,事务管理器bean会默认给一个,但不一定与数据源匹配,所以要是想用@Transcational还是要显示配置一个事务管理器。
1.编写数据源DataSource,此类是一个java提供的接口,常用实现此接口的数据源为HikariDataSource。之后由数据源得到SqlSessionFactoryBean
先导包<dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>5.0.1</version> </dependency>
之后编写DataSource和SqlSessionFactoryBean
@Configuration @ComponentScan({"com.yang"}) @EnableTransactionManagement //@MapperScan("com.yang.dao") public class Config { @Bean public DataSource dataSource() { HikariDataSource dataSource = new HikariDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mybatis"); dataSource.setUsername("root"); dataSource.setPassword("123456"); return dataSource; } @Bean public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); //bean.addMapperLocations(new ClassPathResource("mybatis-config.xml")); //bean.setMapperLocations(new ClassPathResource("CarMapper.xml")); return bean; } @Bean public TransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }
2.配置类添加MpperScan注解--@MapperScan("com.yang.dao"),将Mapper接口交给Spring
3.使用直接getBean即可
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Config.class); CarMapper bean = context.getBean(CarMapper.class); int insert = bean.insert(new Car()); System.out.println(insert);
ps:跑完出现
因为HikariDataSource里用了SLF4J,配置下日志即可。
事务功能具体使用下面介绍。
5.spring-jdbc(事务)
用aop可以实现事务,但事务很常用,spring对其进行了二次封装实现了声明式事务,不用自己编程实现事务了。
专门开发了一套API(对AOP进行封装)
核心接口为PlatformTransactionManager
PlatformTransactionManager:spring事务管理器的核心接口。在Spring6中它有两个实现:
- DataSourceTransactionManager:支持JdbcTemplate、MyBatis、Hibernate等事务管理。
- JtaTransactionManager:支持分布式事务管理。
注解实现声明式事务:
(配置类添加@EnableTransactionManagement
注解即可,这样就可以开启Spring的事务支持了)
1.获取DataSourceTransactionManager(我们整合Mybatis,所以用这个)
(先配DataSource)
@Bean
public TransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
2.添加事务的方法上加注解@Transcational
其中事务属性有什么?
事务中的重点属性:(动力节点这里讲的很细Spring · 语雀)
- 事务传播行为 eg: @Transactional(propagation = Propagation.REQUIRED)
- 事务隔离级别 eg: @Transactional(isolation = Isolation.READ_COMMITTED)
- 事务超时
- 只读事务
- 设置出现哪些异常回滚事务
- 设置出现哪些异常不回滚事务
传播行为:
在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。(一共7种,以下常用)
- REQUIRED:支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】
- REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】
隔离级别:
默认级别为:不可重复读
- ISOLATION_READ_UNCOMMITTED(读未提交):其他事务会读取当前事务尚未更改的提交(相当于读取的是这个事务暂时缓存的内容,并不是数据库中的内容)
- ISOLATION_READ_COMMITTED(读已提交):其他事务会读取当前事务已经提交的数据(也就是直接读取数据库中已经发生更改的内容)
- ISOLATION_REPEATABLE_READ(可重复读):其他事务会读取当前事务已经提交的数据并且其他事务执行过程中不允许再进行数据修改(注意这里仅仅是不允许修改数据)
- ISOLATION_SERIALIZABLE(串行化):它完全服从ACID原则,一个事务必须等待其他事务结束之后才能开始执行,相当于挨个执行,效率很低
事务不起作用:在service层,方法public,事务管理器和SqlSessionFactoryBean必须读同一个DataSource对象(如果SqlSessionFactoryBean单独读配置文件是不会生效的!!!)
6.spring-test(整合Junit)
柏码 - 让每一行代码都闪耀智慧的光芒!
7.spring-webmvc (context功能都有额外有web功能)
回顾:
用servlet:1.创建servlet 2.在web.xml中配置servlet做url映射
执行流程
现在用spring-mvc:
SpringMVC执行流程:
1.通过处理器映射器获得处理器执行链。
1.1一次请求对应一个处理器执行链对象。
1.2该对象有两个重要的属性:
Object类型的handler对象,是HandlerMethod对象,代表要执行的处理器方法。
list集合,里面放的是当前请求要执行的所有拦截器。
2.通过处理器执行链获得处理器适配器。
3.通过处理器执行链执行当前请求对应的所有拦截器的preHandle方法。
4.通过处理器适配器执行处理器方法。
5.通过处理器执行链执行当前请求对应的所有拦截器的postHandle方法。
6.处理分发请求:
6.1通过视图解析器对象的resolveViewName方法,将逻辑视图名解析成物理视图名,返回一个视图对象。
6.2通过视图对象render方法,将模板字符串转变成html代码响应给浏览器,完成渲染。
6.3通过处理器执行链执行当前请求对应的所有拦截器的afterCompletion方法。简单来说:DispatcherServlet接收请求(消息转换器将响应头或响应体转化为参数或pojo传入方法),得到执行链对象,包括要执行的方法(Controller)和拦截器,之后执行拦截器的preHandle再执行Controller,执行完毕后,返回一个ModelAndView对象,之后执行拦截器的postHandle方法,ViewResolver(视图解析器)将逻辑视图转为物理视图(加上前缀和后缀),返回一个视图对象,之后视图对象将内容转为html,消息转化器将html文件写入响应体,DispatcherServlet发给浏览器,完成渲染,最后执行拦截器的afterCompletion方法。
如果我们是发送的ajax请求,只要json字符串,需要加@ResponseBody,这样就不会走视图解析器了,消息转化器将返回的内容写到当前页面。
整个过程我们只需要编写对应请求路径的的Controller以及配置好我们需要的ViewResolver即可,之后还可以继续补充添加拦截器,而其他的流程已经由SpringMVC帮助我们完成了。
配置
在web.xml中配置DispatcherServlet,在初始时读取spring的配置文件,做url映射
采用全注解的方式配置:
1.继承AbstractAnnotationConfigDispatcherServletInitializer类,代替web.xml文件,初始化servlet上下文(tomcat会自动找这个配置类)
2.实现里面的三个方法:
第一个是读取spring的配置类
第二个是读取springMVC的配置类,此配置类上必须加@EnableWebMvc
(第13章 全注解开发 · 语雀 配置Bean汇总)
第三个是配置DispatcherServlet的映射路径
(ps: / 表示除了xxx.jsp以外的所有的路径请求 /*表示所有的路径请求)
(配置分开,也就是springMVC的配置类是负责controller的,在spring上扫controller是没用的,不认识)public class MainInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[]{SpringConfig.class}; } @Override protected Class<?>[] getServletConfigClasses() { return new Class[]{SpringMvcConfig.class}; } @Override protected String[] getServletMappings() { return new String[]{"/"}; } }
spring配置类:负责mybatis整合,事务...
@Configuration @ComponentScan({"com.yang.pojo"}) @EnableTransactionManagement @MapperScan("com.yang.dao") public class SpringConfig { @Bean public DataSource dataSource() { HikariDataSource dataSource = new HikariDataSource(); dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mybatis"); dataSource.setUsername("root"); dataSource.setPassword("123456"); return dataSource; } @Bean public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource) { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); //bean.addMapperLocations(new ClassPathResource("mybatis-config.xml")); //bean.setMapperLocations(new ClassPathResource("CarMapper.xml")); return bean; } @Bean public TransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }
springMVC配置类:负责controller,视图解析器,拦截器,异常...
@Configuration @ComponentScan({"com.yang.controller"}) @EnableWebMvc public class SpringMvcConfig { }
处理静态资源
/ 表示除了xxx.jsp以外的所有的路径请求 /*表示所有的路径请求
也就是说静态资源html,img也走了DispatcherServlet,找不到对应映射路径,报错
解决方案:让静态资源通过Tomcat提供的默认Servlet进行解析,我们需要让配置类实现一下
WebMvcConfigurer
接口,这样在Web应用程序启动时,会根据我们重写方法里面的内容进行进一步的配置public class SpringMvcConfig implements WebMvcConfigurer
@Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer){ configurer.enable(); //开启默认的Servlet } @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**").addResourceLocations("/static/"); //配置静态资源的访问路径 }
之后就可以编写controller和所需要的视图解析器了。
配置视图解析器(以后前后端分离不会这样搞啊,复制过来就好了)
先导包:<dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf-spring6</artifactId> <version>3.1.1.RELEASE</version> </dependency>
@Configuration @ComponentScan({"com.yang.controller"}) @EnableWebMvc public class SpringMvcConfig{ //我们需要使用ThymeleafViewResolver作为视图解析器,并解析我们的HTML页面 @Bean public ThymeleafViewResolver thymeleafViewResolver(SpringTemplateEngine springTemplateEngine){ ThymeleafViewResolver resolver = new ThymeleafViewResolver(); resolver.setOrder(1); //可以存在多个视图解析器,并且可以为他们设定解析顺序 resolver.setCharacterEncoding("UTF-8"); //编码格式是重中之重 resolver.setTemplateEngine(springTemplateEngine); //和之前JavaWeb阶段一样,需要使用模板引擎进行解析,所以这里也需要设定一下模板引擎 return resolver; } //配置模板解析器 @Bean public SpringResourceTemplateResolver templateResolver(){ SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver(); resolver.setSuffix(".html"); //需要解析的后缀名称 resolver.setPrefix("/"); //需要解析的HTML页面文件存放的位置,默认是webapp目录下,如果是类路径下需要添加classpath:前缀 return resolver; } //配置模板引擎Bean @Bean public SpringTemplateEngine springTemplateEngine(ITemplateResolver resolver){ SpringTemplateEngine engine = new SpringTemplateEngine(); engine.setTemplateResolver(resolver); //模板解析器,默认即可 return engine; } }
其中配置html文件所在位置,我们改为classpath:直接放到resourses目录下,webapp目录就没用了。
之后就可以编写controller了。
编写controller
例子:
@Controller
public class MainController {
@ResponseBody
@RequestMapping("/main")
public String hello(){
return "HelloWorld!";
}
@RequestMapping("index")
public String index(){
return "index";
}
}
1)RequestMapping注解
位置:此注解可以放到类上或方法上
属性:
- value和path,可以放路径数组
eg. @RequestMapping(value = {"/testValue1", "/testValue2"})
路径风格:ant("/x?z/testValueAnt") 以及 RESTful(/login/{id}/{username}/{password}) - method,放RequestMethod枚举的数组
RequestMethod枚举类型里有所有的请求方式
共9种,前5种常用:
GET POST PUT POST DELETE (搭配restful风格来使用)HEAD
注意事项:
get请求支持缓存。 也就是说当第二次发送get请求时,会走浏览器上次的缓存结果,不再真正的请求服务器。(有时需要避免,怎么避免:在get请求路径后添加时间戳)
post请求不支持缓存。每一次发送post请求都会真正的走服务器
衍生Mapping
@RequestMapping(value="/login", method = RequestMethod.POST)
@PostMapping("/login") 等同的
其余的也有 -
params,来指定请求必须携带哪些请求参数
eg:
@RequestMapping(value="/login", params={"username", "password"}) 表示:请求参数中必须包含 username 和 password,才能与当前标注的方法进行映射。@RequestMapping(value="/login", params={"username=!admin","password=123"}) -
headers,headers和params原理相同,用法也相同。
当前端提交的请求头信息和后端要求的请求头信息一致时,才能映射成功。
RESTful 风格
发送:
风格:http://localhost:8080/mvc/index/123456
REST对请求方式的约束是这样的:
- 查询必须发送GET请求
- 新增必须发送POST请求
- 修改必须发送PUT请求
- 删除必须发送DELETE请求
(发送put delete请求的前提是一个post请求)
接收:
@RequestMapping("/index/{str}") public String index(@PathVariable("str") String str) { System.out.println(str); return "index"; }
路径接收的名与形参一致可以省略("str"),直接写@PathVariable即可
2)获取请求数据
获取请求提交的数据
1.使用原生的Servlet API进行获取
2.使用RequestParam注解标注
@RequestParam中填写参数名称,参数的值会自动传递给形式参数,我们可以直接在方法中使用,注意,如果参数名称与形式参数名称相同,即使不添加@RequestParam
也能获取到参数值
eg:
public ModelAndView index(@RequestParam("username") String a)
或者 public ModelAndView index(String username)
spring6+要添加参数在pom.xml中,才可以省略
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.12.1</version>
<configuration>
<source>17</source>
<target>17</target>
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
</plugins>
</build>
一旦添加@RequestParam
,那么此请求必须携带指定参数,我们也可以将require属性设定为false来将属性设定为非必须,还可以设置默认值
@RequestParam(value = "username",required = false,defaultValue = "admin")
2.直接传给对象
public ModelAndView index(User user)
注意必须携带set方法或是构造方法中包含所有参数,请求参数会自动根据类中的字段名称进行匹配
3.如果是用restful风格,要用注解@PathVariable加到形参上
4.如果前端返回json数据,直接用pojo对象接收,在pojo形参上加上@RequestBody,消息转化器会自动转为pojo对象
获取请求头信息
使用RequestHeader注解,和RequestParam注解功能相似,RequestParam注解的作用:将请求参数
映射到方法的形参
上。
获取客户端提交的Cookie数据
使用CookieValue注解
如果乱码:
消息转化器底层还是tomcat将请求报文封装成HttpServletRequest,然后用getParameter来得到值,传给形参,所以在DispatcherServlet之前执行request.setCharacterEncoding("UTF-8")
过滤器Filter可以在Servlet执行之前执行。有同学又说了:监听器不行吗?不行。因为我们需要对每一次请求解决乱码,而监听器只在服务器启动阶段执行一次。因此这里解决每一次请求的乱码问题,应该使用过滤器Filter。
SpringMVC已经将这个字符编码的过滤器提前写好了,我们直接配置好即可:CharacterEncodingFilter(具体自己看去)
3)数据共享
1.request域下共享
(request生命周期是一次请求,用在转发时共享数据)
使用ModelAndView
(如果返回的是字符串也会封装为ModelAndView对象,将逻辑视图转为物理视图)
(注意ModelAndView不是出现在方法的参数位置,而是在方法体中new的)
@RequestMapping("index")
public ModelAndView index(String name) {
System.out.println(name);
ModelAndView modelAndView = new ModelAndView("index");
modelAndView.addObject("name", name);
return modelAndView;
}
我们也可以直接用ModelAndView中的Model,可以在形参中获得
使用Model接口,Map接口,ModelMap类均为Model
eg:
@RequestMapping(value = "/index")
public String index(Model model){ //这里不仅仅可以是Model,还可以是Map、ModelMap
model.addAttribute("name", "yyds");
return "index";
}
2.session域下共享
(session生命周期是一次会话,用在重定向数据共享和保持登录状态)
使用SessionAttributes注解
@Controller
@SessionAttributes(value = {"x", "y"})
public class SessionScopeTestController {
@RequestMapping("/testSessionScope2")
public String testSessionAttributes(ModelMap modelMap){
// 向session域中存储数据
modelMap.addAttribute("x", "我是埃克斯");
modelMap.addAttribute("y", "我是歪");
return "view";
}
}
SessionAttributes注解使用在Controller类上。标注了当key是 x 或者 y 时,数据将被存储到会话session中。如果没有 SessionAttributes注解,默认存储到request域中。
3 application(ServletContext)
(ServletContext生命周期服务器,用在统计网站在线人数)
直接使用Servlet API
4)转发和重定向
原理:我们最后全返回一个ModelAndView对象,交给视图解析器之后返回一个view对象
view对象有很多种,转发默认InternalResourceView,重定向默认RedirectView,Thymeleaf解析器创建ThymeleafView。在SpringMVC中是怎么通过代码完成转发的?
@RequestMapping("/a")
public String toA(){
// 返回的是一个逻辑视图名称
return "a";}
注意:当 return"a";的时候,返回了一个逻辑视图名称。这种方式跳转到视图,默认采用的就是 forward方式跳转过去的。只不过这个底层创建的视图对象:ThymeleafView怎么转发?语法格式是什么呢?
return "forward:/b";转发到 /b,这是一次请求,底层创建的视图对象是:InternalResourceView对象。
怎么重定向?语法格式是什么呢?
return "redirect:/b";转发到 /b,这是两次请求,底层创建的视图对象是,RedirectView对象。
5)ajax请求和json
ajax是刷新局部,之前完成此操作就是response.getWriter().print("hello");
将页面写入东西或修改东西。
现在mvc怎么实现呢?
我们可以使用 @ResponseBody 注解来启用对应的消息转换器。而这种消息转换器只负责将Controller返回的信息以响应体的形式写入响应协议,不会走视图解析器了。@ResponseBody +return "hello" --> 用StringHttpMessageConverter消息转换器,将hello直接写入页面
@ResponseBody +return bean --> 用MappingJackson2HttpMessageConverter消息转换器,将bean转我json字符串再写入页面
(用MappingJackson2HttpMessageConverter消息转换器,要导入json和java互相转化的工具包,如下)<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.17.0</version> </dependency>
@RestController这一个注解代表了:@Controller + @ResponseBody。
@RestController 标注在类上即可。被它标注的Controller中所有的方法上都会自动标注 @ResponseBody。
使用注解@RequestBody 放到形参上,将前端发的json数据转为pojo对象
@RequestMapping("/send") @ResponseBody public String send(@RequestBody User user){ System.out.println(user); System.out.println(user.getUsername()); System.out.println(user.getPassword()); return "success"; }
拦截器
柏码 - 让每一行代码都闪耀智慧的光芒!(详细看这里)
我们之前讲解的过滤器是作用于Servlet之前,只有经过层层的过滤器才可以成功到达Servlet,拦截器是在DispatcherServlet将请求交给对应Controller中的方法之前进行拦截处理。
1.创建拦截器 实现 HandlerInterceptor
接口
实现方法 preHandle --> 在Controller中的方法之前运行,如果返回true,才会继续执行controller 方法,返回false是空白,不会继续执行
实现方法postHandle --> 在Controller中的方法之后运行,多级的拦截器会倒着执行
实现方法afterCompletion--> 页面渲染后,执行此方法,多级的拦截器会倒着执行
2.注册
在拦截器注册中注册此拦截器,并设置拦截的路径
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MainInterceptor())
.addPathPatterns("/**") //添加拦截器的匹配路径,只要匹配一律拦截
.excludePathPatterns("/home"); //拦截器不进行拦截的路径
}
异常处理
自定义一个异常处理控制器,一旦出现指定异常,就会转接到此控制器执行。
柏码 - 让每一行代码都闪耀智慧的光芒!(详细看这里)
web错误积累
如果前端发送请求的方式和后端的处理方式不一致时,会出现405错误。
更好的整合方式:第14章 SSM整合 · 语雀
8. Spring补充
Spring高级特性:
柏码 - 让每一行代码都闪耀智慧的光芒!
- Bean Aware
- 任务调度(多线程 异步执行 底层aop开线程)
- 监听器
- SpEL
都是基于ioc,aop的,用到了可以看上面文章
Spring导入外部文件:
- 引入其他配置class @Import({DataSourceConfig.class, MyBatisConfig.class})
- .properties的键值对文件 @PropertySource
此注解是标注在类上的,但是不一定要只能标注在@Configuration注解的类上,只要被Spring管理到的Bean都是可以标注的,然后使用@Value("${}")的方式注入的(SpEL); 只要PropertySource的文件引入一次,该Spring容器中bean都可以使用@Value来设置属性