spring 关键策略
1 基于pojo的轻量级和最小侵入性编程
2 通过依赖注入和面向接口实现松耦合
3 基于切面和惯例进行声明式编程
4 通过切面和模板减少样板式代码
依赖注入 DI
1 耦合在一起 无法更改 只能完成特定Quest
public class BraveKnight implements Knight {
private RescueDamselQuest quest;
public BraveKnight(){
quest = new RescueDamselQuest();
}
public void embarkOnQuest(){
quest.embark();
}
}
2 构造器注入 利用接口参数解除耦合(DI)
public class BraveKnight implements Knight {
private Quest quest;
public BraveKnight(Quest quest){
this.quest = quest;
}
public void embarkOnQuest(){
quest.embark();
}
}
3 创建组建之间协作的行为 装配
构造器注入
<bean id="duke" class="com.wi11iam.springidol.Juggler">
<constructor-arg value="15"/>
</bean>
ApplicationContest ctx = new ClassPathXmlApplicationContext("com/wi11iam/springidol/spring-idol.xml");
Performer performer = (Performer)ctx.getBean("duke");
performer.perform();
package com.wi11iam.springidol;
public class Juggler implements Performer {
private int beanBags = 3;
public Juggler(){}
public Juggler(int beanBags) {
this.beanBags = beanBags;
}
public void perform() {
System.out.println("JUGGLING"+beanBags);
}
}
4注解
@Component 通用的构造型注解 标识该类为Spring组件 Component是这几个的父类
@Controller 标识该类定义为 SpringMVC controllor
@Repository 标记为数据仓库
@Service 标识为服务
@Autowired 让Spring自动装入标识的Bean byType方式
@Inject 同Autowired 只是不同的标准
@Resource byName
@Value 给属性赋值
@Value("${app.url}")
private String url;
如果想要在springMVC的Controllor层也获得配置文件
spring-mvc.xml 也要加扫描配置文件
<context:property-placeholder location="classpath:*.properties" />
5自动扫描
spring-base.xml
<context:component-scan base-package="com.wi11iam.spring">
<context:exclude-filter type="regex" expression="com.wi11iam.spring.controller"/>
</context:component-scan>
spring-mvc.xml 要分开写 两个是两个容器
<context:component-scan base-package="com.wi11iam.spring.controller" />
有了上端就包含了
<context:annotation-config/>
面向切面 AOP
Advice通知 + pointcut切点 = Aspect切面
JoinPoints连接点
Weaving织入 Spring在运行期将切面织入,AOP容器为目标对象动态创建一个代理对象。
通过在代理累中包裹切面,Spring在运行期将切面织入到Spring管理的Bean中,代理类封装了目标类,并拦截被通知的方法得调用,再将调用转发给真正的目标Bean。
1 环绕通知
切面要传入ProceedingJoinPoint 对象实例 用该对象的proceed()方法执行被通知方法(就是真正的目标Bean)
<aop:config>
<aop:aspect ref="audience">
<aop:pointcut id="performance" expression="execution(* com.wi11iam.spring.Performer.perform(..))"/>
<aop:around pointcut-ref="performance" method="watchPerformance()"/>
</aop:aspect>
<aop:config>
public void watchPerformance(ProceedingJoinPoint jointpoint){
long start = System.currentTimeMillis();
joinpoint.proceed();
long end = System.currentTimeMillis();
System.out.println(end-start);
}
2注解的环绕通知
@Aspect
public class WatchPerformance {
@Pointcut("execution(* com.wi11iam.spring.Performer.perform(..))")
public void performance(){}
@Around("performance()")
public void watchPerformance(ProceedingJoinPoint jointpoint){
long start = System.currentTimeMillis();
joinpoint.proceed();
long end = System.currentTimeMillis();
System.out.println(end-start);
}
}
数据访问
Spring的DAO模板类负责通用的数据访问职能,
对于特定任务则调用自定义DAO回调对象。
DAO层将数据访问层和业务层进行分离
Spring对ORM的支持
1.Spring声明式事务的集成支持
2.透明的异常处理
3.线程安全的 轻量级的模版类
4.资源管理
声明式事务(声明式:注解或配置文件)
传播行为
1.REQUIRED 当前方法必须在事务中执行 没有事务开启一个
2.SUPPORTS 不需要在事务中执行 但存在当前事务会在事务中执行
隔离级别 (当前事务受到其他并发事务的影响程度)
1.脏读:一个事务读取了另一个事务改写但尚未提交的数据,如果改写在稍后被回滚,那么第一个事务获取的数据就是无效的。
2.不可重复读:一个事务执行相同的查询两次,但得到不同的数据,这通常是因为另一个并发事务在两次查询期间更新了数据。例如事务T1在读取某一数据,而事务T2立马修改了这个数据并且提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发送了不可重复读。
3.幻读:与不可重复读相似,一个事务读取了几行数据,接着另一个并发事务插入了一些数据,在随后的查询中,第一个事务就会发现多了一些原本不存在的记录。例如事务T1对一个表中所有的行的某个数据项做了从“1”修改为“2”的操作,这时事务T2又对这个表中插入了一行数据项,而这个数据项的数值还是为“1”并且提交给数据库。而操作事务T1的用户如果再查看刚刚修改的数据,会发现还有一行没有修改,其实这行是从事务T2中添加的,就好像产生幻觉一样,这就是发生了幻读。
不可重复读和脏读的区别是,脏读是某一事务读取了另一个事务未提交的脏数据,而不可重复读则是读取了前一事务提交的数据。幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
在理想情况下,事务之间是完全隔离的,从而可以防止这些问题发生,但是完全的事务隔离会导致性能问题,通常会涉及锁定数据库中的记录(表),阻碍并发,要求事务相互等待以完成各自工作。
隔离级别 | 含义 |
---|---|
ISOLATION_DEFAULT | 使用后端数据库默认的隔离级别 |
ISOLATION_READ_UNCOMMITED | 允许读取尚未提交的数据变更 可能会导致脏读 幻读 不可重复读 |
ISOLATION_READ_COMMITED | 允许读取并发事务已经提交的数据 可以阻止脏读 但是幻读或不可重复读扔可能发生 |
ISOLATION_REPEATABLE_READ | 对同一字段的多次读取结果是一致的 除非数据是被本事务自己修改 可以阻止脏读和不可重复读 但幻读仍有可能发生 |
ISOLATION_SERIALIZABLE | 完全服从ACID的隔离级别 确保阻止脏读 不可重复读 幻读 这是最慢的事务隔离级别 通常是通过完全锁定事务相关的数据库表来实现的 |
一般情况是Spring中不配置事务的隔离级别,使用默认的数据库的事务隔离级别,常用的MySQL数据库的InnoDB是Read committed类型的事务隔离级别,(事务的隔离很多是通过数据库的行级锁实现的,一个猜想不一定对)
MySQL的事务隔离级别
隔离级别 | 含义 |
---|---|
Serializable (串行化) | 可避免脏读、不可重复读、幻读的发生。 |
Repeatable read (可重复读) | 可避免脏读、不可重复读的发生。 |
Read committed (读已提交) | 可避免脏读的发生。 |
Read uncommitted (读未提交) | 最低级别,任何情况都无法保证。 |
为了避免幻读和可重复读可以在程序中实现乐观锁(如在数据库中加版本号,更新的时候拿更新前的版本号和现在查出的版本号对比,不同则不更新)。
数据库隔离级别高的话容易发生数据库死锁,如两个两个更新语句都要更改一张表的id为1和100的记录,第一个更新语句(事务1)拿到id为1的记录并在数据库中锁了这条记录,而第二个更新语句(事务2)拿到id为100的记录也在数据库中锁了这条记录,这样事务1还要更新id为100的记录,但这条记录被事务2锁住,同理事务2也拿不到id为1的记录,这样就锁死了2行记录,造成了死锁,据说阿里的某些这种更新操作规定了先从id大的记录先更新这样就规避了中死锁的情况。
只读
声明式事务的第三个特性是否是只读事务。如果事务只对后端的数据库进行读操作,数据库可以利用事物的 只读特性来进行一些特定的优化。通过将事务设置为只读,让数据库对那些具备启动一个新事务的传播行为(REQUIRED,REQUIRED_NEW,NESTED)的方法进行优化,如采用HIbernate作为持久化机制,会导致Hibernate的flush模式被设置成FLUSH_NEVER。这会告诉Hibernate避免和数据库进行不必要的对象异步,并将所有的更新延迟到事务结束。
<!-- 拦截器方式配置事物 -->
<tx:advice id="transactionAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" />
<tx:method name="append*" propagation="REQUIRED" />
<tx:method name="insert*" propagation="REQUIRED" />
<tx:method name="save*" propagation="REQUIRED" />
<tx:method name="update*" propagation="REQUIRED" />
<tx:method name="query*" read-only="true"/>
<tx:method name="put*" read-only="true"/>
<tx:method name="*" propagation="REQUIRED" />
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="transactionPointcut"
expression="(execution(* com.hengxin.qianee.service.impl.*.*(..)))
or (execution(* com.hengxin.qianee.wechat.serviceImpl.*.*(..))) or (execution(* com.hengxin.qianee.talent.wechat.serviceImpl.*.*(..)))" />
<aop:advisor pointcut-ref="transactionPointcut"
advice-ref="transactionAdvice" />
<aop:aspect order="-2147483648" ref="readWriteDataSourceTransactionProcessor">
<aop:around pointcut-ref="transactionPointcut" method="determineReadOrWriteDB"/>
</aop:aspect>
</aop:config>