知识梳理:Spring&SpringAop&MyBatis
目录:Spring&SpringAop&MyBatis
Spring Framework主要包括几个模块:
- 支持IoC和AOP的容器;
- IOC: 依赖注入(Spring容器能力) ,AOP:面向切面编程 .低侵入式为现有业务添加额外功能(切入点编程)
- 支持JDBC和ORM的数据访问模块;
- 支持声明式事务的模块;
- 支持基于Servlet的MVC开发;
- 支持基于Reactive的Web开发;
- 以及集成JMS、JavaMail、JMX、缓存等其他模块。
IoC容器 IOC: 控制反转 Inverse of control DI
Spring的核心就是提供了一个IoC容器,它可以管理所有轻量级的JavaBean组件,提供的底层服务包括组件的生命周期管理、配置和组装服务、AOP支持,以及建立在AOP基础上的声明式事务服务等。
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.softeem</groupId>
<artifactId>spring_first</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<java.version>1.8</java.version>
<spring.version>5.2.3.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
</project>
在resources下新建文件 beans.xml
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userDao" class="com.softeem.dao.impl.UserDaoImpl"></bean>
<bean id="userService" class="com.softeem.service.UserService">
<property name="userDao" ref="userDao"></property>
</bean>
</beans>
控制反转(Inversion of Control)就是依赖倒置原则的一种代码设计的思路。具体采用的方法就是所谓的依赖注入(Dependency Injection)。
使用Annotation配置
使用Spring的IoC容器,实际上就是通过类似XML这样的配置文件,把我们自己的Bean的依赖关系描述出来,然后让容器来创建并装配Bean。一旦容器初始化完毕,我们就直接从容器中获取Bean使用它们。
Spring默认使用Singleton创建Bean,也可指定Scope为Prototype;
可将相同类型的Bean注入List
;
可用@Autowired(required=false)
允许可选注入;
可用使用@Bean
标注的方法创建Bean; bean:被容器所管理的类,我们自己定义的类,标记了component之后也是被容器管理的组件,@Bean在spring中通常用来引入第三方组件
可使用@PostConstruct
和@PreDestroy
对Bean进行初始化和清理;
相同类型的Bean只能有一个指定为@Primary
,其他必须用@Quanlifier("beanName")
指定别名;
注入时,可通过别名@Quanlifier("beanName")
指定某个Bean;
SpringAop
AOP是Aspect Oriented Programming,即面向切面编程。
OOP:Object Oriented Programming,OOP作为面向对象编程的模式,获得了巨大的成功,OOP的主要功能是数据封装、继承和多态。
而AOP是一种新的编程方式,它和OOP不同,OOP把系统看作多个对象的交互,AOP把系统分解为不同的关注点,或者称之为切面(Aspect)。
AOP原理
如何把切面织入到核心逻辑中?这正是AOP需要解决的问题。换句话说,如果客户端获得了BookService
的引用,当调用bookService.createBook()
时,如何对调用方法进行拦截,并在拦截前后进行安全检查、日志、事务等处理,就相当于完成了所有业务功能。
在Java平台上,对于AOP的织入,有3种方式:
- 编译期:在编译时,由编译器把切面调用编译进字节码,这种方式需要定义新的关键字并扩展编译器,AspectJ就扩展了Java编译器,使用关键字aspect来实现织入;
- 类加载器:在目标类被装载到JVM时,通过一个特殊的类加载器,对目标类的字节码重新“增强”;
- 运行期:目标对象和切面都是普通Java类,通过JVM的动态代理功能或者第三方库实现运行期动态织入。
最简单的方式是第三种,Spring的AOP实现就是基于JVM的动态代理。由于JVM的动态代理要求必须实现接口,如果一个普通类没有业务接口,就需要通过CGLIB或者Javassist这些第三方库实现。
AOP技术看上去比较神秘,但实际上,它本质就是一个动态代理,让我们把一些常用功能如权限检查、日志、事务等,从每个业务方法中剥离出来。
需要特别指出的是,AOP对于解决特定问题,例如事务管理非常有用,这是因为分散在各处的事务代码几乎是完全相同的,并且它们需要的参数(JDBC的Connection)也是固定的。另一些特定问题,如日志,就不那么容易实现,因为日志虽然简单,但打印日志的时候,经常需要捕获局部变量,如果使用AOP实现日志,我们只能输出固定格式的日志,因此,使用AOP时,必须适合特定的场景。
Spring对接口类型使用JDK动态代理,对普通类使用CGLIB创建子类。如果一个Bean的class是final,Spring将无法为其创建子类。
虽然Spring容器内部实现AOP的逻辑比较复杂(需要使用AspectJ解析注解,并通过CGLIB实现代理类),但我们使用AOP非常简单,一共需要三步:
- 定义执行方法,并在方法上通过AspectJ的注解告诉Spring应该在何处调用此方法;
- 标记
@Component
和@Aspect
; - 在
@Configuration
类上标注@EnableAspectJAutoProxy
。
Spring也提供其他方法来装配AOP,但都没有使用AspectJ注解的方式来得简洁明了。
代理模式
java中的代理模式有两种,一种为静态代理,一种为动态代理
静态代理要求委托人,和代理具备相同能力(实现相同接口)
动态代理,不要求代理和委托人实现相同的接口,委托人必须有实现过接口(必须有类型扩展)
- 代理实现 InvocationHandler
- 代理对象 Proxy
使用声明式事务
使用Spring操作JDBC虽然方便,但是我们在前面讨论JDBC的时候,讲到过JDBC事务,如果要在Spring中操作事务,没必要手写JDBC事务,可以使用Spring提供的高级接口来操作事务。
Spring提供了一个PlatformTransactionManager
来表示事务管理器,所有的事务都由它负责管理。而事务由TransactionStatus
表示。
Spring为啥要抽象出PlatformTransactionManager
和TransactionStatus
?原因是JavaEE除了提供JDBC事务外,它还支持分布式事务JTA(Java Transaction API)。分布式事务是指多个数据源(比如多个数据库,多个消息系统)要在分布式环境下实现事务的时候,应该怎么实现。分布式事务实现起来非常复杂,简单地说就是通过一个分布式事务管理器实现两阶段提交,但本身数据库事务就不快,基于数据库事务实现的分布式事务就慢得难以忍受,所以使用率不高。
Spring为了同时支持JDBC和JTA两种事务模型,就抽象出PlatformTransactionManager
。因为我们的代码只需要JDBC事务,因此,在AppConfig
中,需要再定义一个PlatformTransactionManager
对应的Bean,它的实际类型是DataSourceTransactionManager
:
@Configuration
@ComponentScan
@PropertySource("jdbc.properties")
public class AppConfig {
...
@Bean
PlatformTransactionManager createTxManager(@Autowired DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
使用编程的方式使用Spring事务仍然比较繁琐,更好的方式是通过声明式事务来实现。使用声明式事务非常简单,除了在AppConfig
中追加一个上述定义的PlatformTransactionManager
外,再加一个@EnableTransactionManagement
就可以启用声明式事务:
@Configuration
@ComponentScan
@EnableTransactionManagement // 启用声明式
@PropertySource("jdbc.properties")
public class AppConfig {
...
}
然后,对需要事务支持的方法,加一个@Transactional
注解:
@Component
public class UserService {
// 此public方法自动具有事务支持:
@Transactional
public User register(String email, String password, String name) {
...
}
}
或者更简单一点,直接在Bean的class
处加上,表示所有public
方法都具有事务支持:
@Component
@Transactional
public class UserService {
...
}
Spring对一个声明式事务的方法,如何开启事务支持?原理仍然是AOP代理,即通过自动创建Bean的Proxy实现:
public class UserService$$EnhancerBySpringCGLIB extends UserService {
UserService target = ...
PlatformTransactionManager txManager = ...
public User register(String email, String password, String name) {
TransactionStatus tx = null;
try {
tx = txManager.getTransaction(new DefaultTransactionDefinition());
target.register(email, password, name);
txManager.commit(tx);
} catch (RuntimeException e) {
txManager.rollback(tx);
throw e;
}
}
...
}
注意:声明了@EnableTransactionManagement
后,不必额外添加@EnableAspectJAutoProxy
。
BonusService.addBonus()方法必须要有
@Transactional`,否则,登录后积分就无法添加了。
默认的事务传播级别是REQUIRED
,它满足绝大部分的需求。还有一些其他的传播级别:
REQUIRED:默认,如果有事务在运行,当前的方法就在这个事务内运行,否则,就启动一个新的事务,并在自己的事务内运行
SUPPORTS:表示如果有事务,就加入到当前事务,如果没有,那也不开启事务执行。这种传播级别可用于查询方法,因为SELECT语句既可以在事务内执行,也可以不需要事务;
MANDATORY:表示必须要存在当前事务并加入执行,否则将抛出异常。这种传播级别可用于核心更新逻辑,比如用户余额变更,它总是被其他事务方法调用,不能直接由非事务方法调用;
REQUIRES_NEW:表示不管当前有没有事务,都必须开启一个新的事务执行。如果当前已经有事务,那么当前事务会挂起,等新事务完成后,再恢复执行;
NOT_SUPPORTED:表示不支持事务,如果当前有事务,那么当前事务会挂起,等这个方法执行完成后,再恢复执行;
NEVER:和NOT_SUPPORTED相比,它不但不支持事务,而且在监测到当前有事务时,会抛出异常拒绝执行;
NESTED:表示如果当前有事务,则开启一个嵌套级别事务,如果当前没有事务,则开启一个新事务。
MyBatis
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射(ORM)。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
使用Mybatis
- 引入相关的pom支持
- 在Configuration(AppConfig.java)中配置SqlSessionFactory到容器,该对象需要传入一个DataSource数据源
- 编写Mapper接口,定义对应的方法。
- 添加注解,写入SQL语句
- 在Configuration类中添加注解@MapperScan去扫描mybatis的mapper
- 运行
@Configuration
@ComponentScan
@PropertySource("jdbc.properties")
@MapperScan("com.softeem.mapper")
public class AppConfig {
配置文件方式
mybatis配置文件示例
<?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.softeem.mapper.BookMapper">
<resultMap id="BaseResultMap" type="com.softeem.entity.Book">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="cover" jdbcType="VARCHAR" property="cover" />
<result column="title" jdbcType="VARCHAR" property="title" />
<result column="author" jdbcType="VARCHAR" property="author" />
<result column="date" jdbcType="VARCHAR" property="date" />
<result column="press" jdbcType="VARCHAR" property="press" />
<result column="abs" jdbcType="VARCHAR" property="abs" />
<result column="cid" jdbcType="INTEGER" property="cid" />
</resultMap>
<sql id="Base_Column_List">
id, cover, title, author, `date`, press, `abs`, cid
</sql>
<select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from book
where id = #{id,jdbcType=INTEGER}
</select>
<delete id="deleteByPrimaryKey" parameterType="java.lang.Integer">
delete from book
where id = #{id,jdbcType=INTEGER}
</delete>
<insert id="insert" keyColumn="id" keyProperty="id" parameterType="com.softeem.entity.Book" useGeneratedKeys="true">
insert into book (cover, title, author,
`date`, press, `abs`, cid
)
values (#{cover,jdbcType=VARCHAR}, #{title,jdbcType=VARCHAR}, #{author,jdbcType=VARCHAR},
#{date,jdbcType=VARCHAR}, #{press,jdbcType=VARCHAR}, #{abs,jdbcType=VARCHAR}, #{cid,jdbcType=INTEGER}
)
</insert>
<insert id="insertSelective" keyColumn="id" keyProperty="id" parameterType="com.softeem.entity.Book" useGeneratedKeys="true">
insert into book
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="cover != null">
cover,
</if>
<if test="title != null">
title,
</if>
<if test="author != null">
author,
</if>
<if test="date != null">
`date`,
</if>
<if test="press != null">
press,
</if>
<if test="abs != null">
`abs`,
</if>
<if test="cid != null">
cid,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="cover != null">
#{cover,jdbcType=VARCHAR},
</if>
<if test="title != null">
#{title,jdbcType=VARCHAR},
</if>
<if test="author != null">
#{author,jdbcType=VARCHAR},
</if>
<if test="date != null">
#{date,jdbcType=VARCHAR},
</if>
<if test="press != null">
#{press,jdbcType=VARCHAR},
</if>
<if test="abs != null">
#{abs,jdbcType=VARCHAR},
</if>
<if test="cid != null">
#{cid,jdbcType=INTEGER},
</if>
</trim>
</insert>
<update id="updateByPrimaryKey" parameterType="com.softeem.entity.Book">
update book
set cover = #{cover,jdbcType=VARCHAR},
title = #{title,jdbcType=VARCHAR},
author = #{author,jdbcType=VARCHAR},
`date` = #{date,jdbcType=VARCHAR},
press = #{press,jdbcType=VARCHAR},
`abs` = #{abs,jdbcType=VARCHAR},
cid = #{cid,jdbcType=INTEGER}
where id = #{id,jdbcType=INTEGER}
</update>
</mapper>
上一篇: NTFS是什么?NTFS格式分区是什么意思又该如何转换和注意事项?
下一篇: sql经典面试题分享