简化控制反转
spring 2.5开始, 提供了注解方式的控制反转和依赖注入。
注解:
@Component 加在类上,spring 扫描到它之后,就把它交给 spring 容器管理
@Controller(表现层或叫控制层)
@Service(业务逻辑层或叫服务层)
@Repository(对应数据访问层)
@Component(不属于前3层,不好分类时)
@PostConstruct (在...之后 构造方法)
@PreDestroy(在...之前 销毁)
控制单例多例子: @Scope("singleton|prototype")
控制初始化和销毁方法:@PostConstruct 用来标记初始化方法, @PreDestroy 用来标记销毁方法
控制懒惰初始化: @Lazy 加在类上,表示这个类在容器中是懒惰初始化的注解方式的缺点在于,不能管理第三方的 class ,例如连接池等
代码演示
1、注解演示
接口
public interface UserDao {
public void insert();
public void update();
}
接口实现
import org.springframework.stereotype.Repository;
@Repository("userDao") // 等价于 <bean id="userDao" class="dao.impl.UserDaoImpl">
public class UserDaoImpl implements UserDao {
@Override
public void insert() {
System.out.println("UserDaoImpl insert");
}
@Override
public void update() {
System.out.println("UserDaoImpl update");
}
}
spring的xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"
>
<!-- 启用注解: @Autowired @Component ... -->
<!--<context:annotation-config></context:annotation-config>-->
<!-- 缩小搜索范围,让spring更快找到要搜索的类
扫描加了 @Component 注解的类, 把加了此注解的类交给 spring 管理
base-package="起始包名1,起始包名2...",可以分成两份写,如下面的两个context
component-scan 里面涵盖了 annotation-config 的功能,所以可以省略前面的配置
-->
<context:component-scan base-package="service"/>
<context:component-scan base-package="dao"/>
</beans>
使用注解
import dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
//@Component 也可以写这个代替Service,不过Component不比Service层次分明,最好是什么层写那个层的注解
@Service("userService") // 等价于 <bean id="userService" class="service.UserService">
@Scope("prototype")
public class UserService {
//依赖注入,这样就可以不用new对象,就调用方法
@Autowired
private UserDao userDao;
//构造方法
public UserService(){
System.out.println("执行构造方法");
}
public void insert() {
System.out.println("UserService insert");
userDao.insert();
}
public void update() {
System.out.println("UserService update");
}
@PostConstruct // 在...之后 构造方法
public void a() {
System.out.println("UserService 初始化方法");
}
@PreDestroy // 在...之前 销毁
public void b() {
System.out.println("UserService 销毁方法");
}
}
测试类
import org.springframework.context.support.ClassPathXmlApplicationContext;
import service.UserService;
public class TestSpring {
public static void main(String[] args) {
// 创建容器
ClassPathXmlApplicationContext context =
new ClassPathXmlApplicationContext("spring.xml");
// 获取对象
UserService service = context.getBean(UserService.class);
// 调用方法
service.insert();
UserService service2 = context.getBean(UserService.class);
//输出是false,因为前面设置了scope(设置单例或双例)为property(多例,也就是调用的class为多个对象),如果是singleton(单例,也就是调用的class单个对象)那么就是true
System.out.println(service == service2);
context.close();
}
}
结果
执行构造方法
UserService 初始化方法
UserService insert
执行构造方法
UserService 初始化方法
false
从结果可以看出@PostConstruct修饰的方法是在构造方法之后执行,@PreDestroy修饰的方法是在销毁前调用,@Scope控制单例和多例,让从class中拿到的对象,是否为同一个对象。
声明式事务介绍
spring 中启用事务注解 让 @Transactional 注解生效
<tx:annotation-driven/>
jdbc中提交和回滚事务
connection.commit();
connection.rollback();mybatis中提交和回滚事务
sqlSession.commit();
sqlSession.rollback();
选择事务管理器
事务管理器类,它来负责具体的事务管理操作(commit, rollback...)
- DataSourceTransactionManager (对mybatis 来讲,选这个实现)
- HibernateTransactionManager (对hibernate框架来讲,选这个实现)配置:里面的property是连接到连接池,因为事物最终是要提交的,提交给连接池,连接池取连接数据库去找结果
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>spring 中如果 @Transactional 的方法出现了 RuntimeException 或 Error , 事务回滚,@Transactional 的方法出现了 Exception 事务提交,如果希望遇到 Exception 也回滚事务 @Transational(rollbackFor=要回滚的异常类.class)。
@Transactional的相关配置
- readOnly 只读事务
如果事务内没有增删改操作,只有查询操作,可以把事务设置为 readOnly=true,能提高一些性能,如果在只读事务内执行增删改会报异常。
- timeout 事务的超时时间
事务的超时时间, 一旦事务运行超过了这个时间,就会结束事务,并回滚,超时时间的意义在于防止某个长时间运行的事务影响整个系统的性能,事务越短越好。
- isolation 隔离级别 (隔离级别越高,一致性越好,但性能越差)
未提交读 - 脏读、不可重复读、幻读
提交读 - 不可重复读、幻读 (oracle, sqlserver...默认的隔离级别)
可重复读 - 幻读 (mysql默认)
序列化读 - 没有上面的问题
- propagation 传播行为
Propagation.REQUIRED 必须的,如果没有事务那么开始事务,如果有事务就加入 (默认的)
Propagation.SUPPORTS 支持的, 不会主动开始事务,但是会加入已经存在的事务
Propagation.REQUIRES_NEW 需要新的, 每次都会开始新事务@Service
class ServiceA {
@Transactional(propagation=Propagation.REQUIRES_NEW) // tx1
public void m1() {
m2();
}
}@Service
class ServiceB {
@Transactional(propagation=Propagation.REQUIRES_NEW) // tx2
public void m2() {}
}
- 如果两个都加上SUPPORTS,当m1开始执行时,事物不会主动开始,当调用m2时,m2也不会主动开始事物,也就是说,在这过程中没有新事物产生,这些代码没有工作在同一事物内,回滚是只会个滚个的;
- 如果m1是REQUIRED,m2是SUPPORTS,当m1开始执行时,还没有事物产生,因为REQUIRED是事物必须的,需要开始一个新事物tx1,等到调用m2时,因为m2是SUPPORTS,有事物就加入事物,而上面的tx1就是一个事物,因此m2就加入tx1,它们m2和m2就在同一个事物内工作,那么回滚时,它们会一起滚;
- 但是以上地位中情况相反反过来标明,回滚时还是个滚个的,因为m1不会主动开始新事物,也没有新事物加入,调用m2时才会产生一个新事物tx1,它和m1没有联系,它们就不在同一个事物内工作,因此个滚个;
- 两个都是REQUIRES_NEW,当m1开始执行时,需要新的事物产生tx1,挡调用m2,也需要一个新事物产生tx2,,也就是这个过程中产生了两个新的事物,其中一个事物回滚不会影响到另一个事物回滚;
代码演示
@Transactional代码演示
spring.xml中的配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"
>
<!--<bean id="dataSource"
class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
<property name="maxActive" value="10"/>
<property name="minIdle" value="2"/>
</bean>-->
<!-- 1. 配置了连接池 -->
<bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/hero?useSSL=false"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
<property name="maximumPoolSize" value="10"/>
<property name="minimumIdle" value="2"/>
</bean>
<!-- 2. 配置 sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 把连接池对象,依赖注入给 SqlSessionFactoryBean 的dataSource 属性 -->
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 扫描 有哪些 mapper 接口需要交给 spring 管理
<bean id="heroDao">
<bean id="userDao">
-->
<bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.westos.dao"/>
</bean>
<!-- 4. 启用事务注解 -->
<tx:annotation-driven transaction-manager="aaa"/>
<!-- 5. 配置事务管理器 ,不用 transaction-manager=“名字”,下面的id就要写为transactionManager,否则就写transaction-manager后面等于的名字-->
<bean id="aaa" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 6. 扫描 service 类 -->
<context:component-scan base-package="com.westos.service"/>
</beans>
}
接口结合mybatis写删除语句
public interface HeroDao {
//根据id删除信息
@Delete("delete from hero where id=#{id}")
public void delete(int id);
}
写事务代码,故意写出错代码
import com.westos.dao.HeroDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
public class HeroService {
@Autowired
private HeroDao heroDao;
// 没用 spring 之前
/*public void deleteByIds(List<Integer> ids) {
// 设置事务手动提交
try {
for (Integer id : ids) {
heroDao.delete(id);
}
// 提交事务
} catch (Exception e) {
// 回滚事务
}
}*/
//用spring之后
@Transactional(rollbackFor = Exception.class)
public void deleteByIds(List<Integer> ids) throws Exception{
//故意设置一个异常,当id等于8时,抛出异常
for (Integer id : ids) {
if (id == 8) {
throw new Exception("故意出现了异常");
}
heroDao.delete(id);
}
}
}
测试类
import com.westos.service.HeroService;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.Arrays;
public class TestTransaction {
public static void main(String[] args) throws Exception {
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
HeroService heroService = context.getBean(HeroService.class);
// 成功了,没有异常出现,spring 会提交事务
// heroService.deleteByIds(Arrays.asList(1,2,3));
// 执行过程中出现了异常, spring 会回滚事务
heroService.deleteByIds(Arrays.asList(6,7,8));
}
}
结果
Exception in thread "main" java.lang.Exception: 故意出现了异常
上面抛出了异常,事务回滚,数据库中的数据没有删除到。
总结
配置太多优点脑壳疼,事务管理这里不这么重要知道怎么用就行,控制反转比较重要,在以后的项目中会经常出现,要弄明白那些标签的作用及其用法,这样代码量会减少很多,看着也简单易懂。
总之,爱生活,爱Java。