spring面试题(2020最新版)

    科技2024-09-29  18

    spring中的配置文件命名空间和约束

    bean名称空间和约束的配置文件bean.xml

    基于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"> </beans>

    bean和context名称空间和约束的配置文件bean.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" xmlns:context="http://www.springframework.org/schema/context" 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"> </beans>

    使用到aop

    bean和aop名称空间和约束的配置文件bean.xml

    基于xml配置文件前缀:

    <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> </beans>

    bean、context和aop名称空间和约束的配置文件bean.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" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> </beans>

    把创建的对象注入spring容器中范例

    项目结构: 配置文件:bean.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"> <!--把对象的创建交给spring来管理 --> <bean id="accountService" class="com.hunan.tabu.service.impl.AccountServiceImpl"></bean> <bean id="accountDao" class="com.hunan.tabu.dao.impl.AccountDaoImpl"></bean> </beans>

    client.java:

    public static void main(String[] args) { //1.获取核心容器对象 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.根据id获取Bean对象 IAccountService as = (IAccountService)ac.getBean("accountService"); IAccountDao adao = ac.getBean("accountDao",IAccountDao.class); System.out.println(as); System.out.println(adao); }

    运行结果:

    ApplicationContext的三个常用实现类

    ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了。(更常用) 如下图:

    FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限)

    AnnotationConfigApplicationContext:它是用于读取注解创建容器

    核心容器两个接口ApplicationContext和BeanFactory的区别:

    如下代码:bean.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"> <!--把对象的创建交给spring来管理--> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean> <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"></bean> </beans>

    AccountServiceImpl .java:

    public class AccountServiceImpl implements IAccountService { public AccountServiceImpl(){ System.out.println("对象创建了"); } } ApplicationContext:(更常用) 1.它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象。 2.适用于单例对象

    client.java:

    //1.获取核心容器对象 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.根据id获取Bean对象 IAccountService as = (IAccountService)ac.getBean("accountService"); IAccountDao adao = ac.getBean("accountDao",IAccountDao.class)

    在运行完第2行之后,即读取完配置文件后,马上就创建配置文件中AccountServiceImpl类的对象。

    运行结果:

    BeanFactory: 1.它在构建核心容器时,创建对象采取的策略是采用延迟加载(按需加载)的方式。也就是说,什么时候根据id获取对象了,什么时候才真正的创建配置文件中对象。 2.适用于多例对象

    Client.java:

    //--------BeanFactory---------- Resource resource = new ClassPathResource("bean.xml"); BeanFactory factory = new XmlBeanFactory(resource); IAccountService as = (IAccountService)factory.getBean("accountService"); System.out.println(as);

    在运行完第4行后,即根据accountService获取对象后,开始创建配置文件中的对象 运行结果:

    spring对bean的管理细节

    创建bean的三种方式

    方式一:使用默认构造函数创建对象

    在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时,采用的就是默认构造函数创建bean对象。此时如果类中没有默认构造函数,则对象无法创建。

    配置文件bean.xml:

    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>

    AccountServiceImpl.java:

    public class AccountServiceImpl implements IAccountService { //默认构造函数 public AccountServiceImpl(){ System.out.println("对象创建了"); } }

    运行结果: 但是如下AccountServiceImpl.java无默认构造函数,无法创建bean对象:

    public class AccountServiceImpl implements IAccountService { //因为创建了带参构造函数,所以系统不会再创建默认构造函数,此时类中没有默认构造函数 public AccountServiceImpl(String name){ System.out.println("对象创建了"); } } 方式二:使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)

    InstanceFactory .java:

    public class InstanceFactory { public IAccountService getAccountService(){ return new AccountServiceImpl(); } }

    bean.xml:

    <bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean> <bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>

    运行结果: 3. 方式三:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)

    StaticFactory .java:

    public class StaticFactory { public static IAccountService getAccountService(){ return new AccountServiceImpl(); } }

    bean.xml:

    <bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="getAccountService"></bean>

    运行结果:

    bean对象的作用范围

    bean标签的scope属性: -------作用:用于指定bean的作用范围 -------取值: 常用的就是单例的和多例的 -------------singleton:单例的(默认单例) -------------prototype:多例的 -------------request:作用于web应用的请求范围 -------------session:作用于web应用的会话范围 -------------global-session:作用于集群环境的会话范围

    bean.xml:

    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="prototype"></bean>

    client.java:

    public static void main(String[] args) { //1.获取核心容器对象 // ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.根据id获取Bean对象 IAccountService as = (IAccountService)ac.getBean("accountService"); IAccountService as2 = (IAccountService)ac.getBean("accountService"); System.out.println(as); System.out.println(as2);

    运行结果:

    bean对象的生命周期

    单例对象: 出生:当容器创建时对象出生 活着:容器还在,对象一直活着 死亡:容器销毁,对象死亡

    AccountServiceImpl .java:

    public class AccountServiceImpl implements IAccountService { public AccountServiceImpl(){ System.out.println("对象创建了"); } public void saveAccount(){ System.out.println("service中的saveAccount方法执行了。。。"); } public void init(){ System.out.println("对象初始化了。。。"); } public void destroy(){ System.out.println("对象销毁了。。。"); } }

    bean.xml:

    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="singleton" init-method="init" destroy-method="destroy"></bean>

    client.java:

    public static void main(String[] args) { //1.获取核心容器对象 ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.根据id获取Bean对象 IAccountService as = (IAccountService)ac.getBean("accountService"); as.saveAccount(); //手动关闭容器 ac.close(); }

    运行结果:

    多例对象: 出生:当需要使用对象时,spring框架为我们创建 活着:当只要在使用时,对象就一直活着 死亡:当对象长时间不用时,且没有别的对象引用,垃圾回收器回收

    bean.xml:

    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="prototype" init-method="init" destroy-method="destroy"></bean>

    运行结果:

    标签注入数据

    注入数据的三种方式

    方式一:构造函数注入: 使用的标签:constructor-arg 标签出现的位置:bean标签的内部 标签中的属性: *******type:指定要注入的数据的数据类型,该数据类型也是构造函数中某些参数的类型 *******index:指定要注入的数据对应构造函数中指定索引位置的参数赋值。索引的位置是从0开始 *******name:指定构造函数中指定名称的参数赋值 (常用的) 以上三个都是用于指定给构造函数中哪个参数赋值================= value:为基本类型和String类型的数据赋值 ref:指定其他的bean类型数据。它指的就是在spring Ioc核心容器中出现过的bean对象

    AccountServiceImpl .java:

    public class AccountServiceImpl implements IAccountService { //如果是经常变化的数据,并不适用于注入的方式 private String name; private Integer age; private Date birthday; public AccountServiceImpl(String name,Integer age,Date birthday){ this.name = name; this.age = age; this.birthday = birthday; } public void saveAccount(){ System.out.println("service中的saveAccount方法执行了。。。"+name+","+age+","+birthday); } }

    bean.xml:

    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <constructor-arg name="name" value="泰斯特"></constructor-arg> <constructor-arg name="age" value="18"></constructor-arg> <constructor-arg name="birthday" ref="now"></constructor-arg> </bean> <!-- 配置一个日期对象 --> <bean id="now" class="java.util.Date"></bean>

    client.java:

    public static void main(String[] args) { //1.获取核心容器对象 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.根据id获取Bean对象 IAccountService as = (IAccountService)ac.getBean("accountService"); as.saveAccount(); }

    运行结果:

    方式二:set方法注入(常用方式) 使用的标签:property 出现的位置:bean标签的内部 标签的属性: ****name: 所调用的set方法后面的名称 ******value:给基本类型和String类型的数据赋值 ******ref:指定其他的bean类型数据。它指的就是在spring Ioc核心容器中出现过的bean对象

    AccountServiceImpl2 .java:

    public class AccountServiceImpl2 implements IAccountService { //如果是经常变化的数据,并不适用于注入的方式 private String name; private Integer age; private Date birthday; public void setUserName(String name) { this.name = name; } public void setAge(Integer age) { this.age = age; } public void setBirthday(Date birthday) { this.birthday = birthday; } public void saveAccount(){ System.out.println("service中的saveAccount方法执行了。。。"+name+","+age+","+birthday); } }

    bean.xml:

    <bean id="accountService2" class="com.itheima.service.impl.AccountServiceImpl2"> <property name="userName" value="TEST" ></property> <property name="age" value="21"></property> <property name="birthday" ref="now"></property> </bean> <!-- 配置一个日期对象 --> <bean id="now" class="java.util.Date"></bean>

    运行结果:

    方式三:给集合类型的注入 给List集合注入的标签: list array set 给Map集合注入的标签: map props

    AccountServiceImpl3 .java:

    public class AccountServiceImpl3 implements IAccountService { private String[] myStrs; private List<String> myList; private Set<String> mySet; private Map<String,String> myMap; private Properties myProps; public void setMyStrs(String[] myStrs) { this.myStrs = myStrs; } public void setMyList(List<String> myList) { this.myList = myList; } public void setMySet(Set<String> mySet) { this.mySet = mySet; } public void setMyMap(Map<String, String> myMap) { this.myMap = myMap; } public void setMyProps(Properties myProps) { this.myProps = myProps; } public void saveAccount(){ System.out.println(Arrays.toString(myStrs)); System.out.println(myList); System.out.println(mySet); System.out.println(myMap); System.out.println(myProps); } }

    bean.xml:

    <bean id="accountService3" class="com.itheima.service.impl.AccountServiceImpl3"> <property name="myStrs"> <set> <value>AAA</value> <value>BBB</value> <value>CCC</value> </set> </property> <property name="myList"> <array> <value>AAA</value> <value>BBB</value> <value>CCC</value> </array> </property> <property name="mySet"> <list> <value>AAA</value> <value>BBB</value> <value>CCC</value> </list> </property> <property name="myMap"> <props> <prop key="testC">ccc</prop> <prop key="testD">ddd</prop> </props> </property> <property name="myProps"> <map> <entry key="testA" value="aaa"></entry> </map> </property> </bean>

    运行结果:

    注解(重点)

    用于创建对象的注解

    @Component ***作用地方:类上 ***作用:把当前类对象存入spring容器中 ***属性: **********value:指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写。

    bean.xml:

    <!--创建容器时,扫描com.itheima下的包及子包中类和接口的注解--> <context:component-scan base-package="com.itheima"></context:component-scan>

    AccountServiceImpl .java:

    @Component(value = "accountService") public class AccountServiceImpl implements IAccountService { public AccountServiceImpl() { System.out.println("对象创建了"); } }

    client.java:

    public static void main(String[] args) { //1.获取核心容器对象 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.根据id获取Bean对象 IAccountService as = (IAccountService)ac.getBean("accountService"); System.out.println(as); }

    运行结果:

    @Controller:一般用在表现层

    @Service:一般用在业务层

    @Repositrory:一般用在持久层 以上三个注解他们的作用和属性与@Component是一模一样。他们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰

    用于注入数据的注解

    @Autowired: ******作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功。 如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。 ***** 出现位置: 可以是变量上,也可以是方法上 ******细节: 在使用注解注入时,set方法就不是必须的了。

    spring的IOC容器注入数据原理

    单个注入类型匹配: 多个注入类型匹配: 运行结果:

    NoUniqueBeanDefinitionException: No qualifying bean of type 'com.itheima.dao.IAccountDao' available: expected single matching bean but found 2: accountDao1,accountDao2

    多个注入类型匹配解决方案: 匹配原则: 当注入的数据类型和容器中多个bean对象类型匹配时,使用变量名和bean对象中id进行匹配。

    AccountServiceImpl .java:

    AccountDaoImpl .java:

    @Repository("accountDao1") public class AccountDaoImpl implements IAccountDao { public void saveAccount(){ System.out.println("保存了账户1111111111111"); } }

    AccountDaoImpl2 .java:

    @Repository("accountDao2") public class AccountDaoImpl2 implements IAccountDao { public void saveAccount(){ System.out.println("保存了账户2222222222222"); } }

    运行结果: 使用@Qualifier注解解决多个类型注入:

    @Qualifier: ***作用:给类成员注入时不能单独使用,要和@Autowired联合使用,但是给方法参数注入时可以。 *** 属性: *********value:指定注入bean的id。

    AccountServiceImpl .java:

    @Component(value = "accountSer") public class AccountServiceImpl implements IAccountService { @Autowired @Qualifier("accountDao1") private IAccountDao accountDao; public AccountServiceImpl() { System.out.println("对象创建了"); } public void saveAccount(){ accountDao.saveAccount(); } }

    AccountDaoImpl .java:

    @Repository("accountDao1") public class AccountDaoImpl implements IAccountDao { public void saveAccount(){ System.out.println("保存了账户1111111111111"); } } Resource ******作用:直接按照bean的id注入。它可以独立使用 ******属性: ***********name:用于指定bean的id。 @Component(value = "accountSer") public class AccountServiceImpl implements IAccountService { @Resource(name = "accountDao2") private IAccountDao accountDao; public AccountServiceImpl() { System.out.println("对象创建了"); } public void saveAccount(){ accountDao.saveAccount(); } }

    AccountDaoImpl2.java:

    @Repository("accountDao2") public class AccountDaoImpl2 implements IAccountDao { public void saveAccount(){ System.out.println("保存了账户2222222222222"); }

    以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。 另外,集合类型的注入只能通过XML来实现。

    @Value ******作用:注入基本类型和String类型的数据 ******属性: *********value:指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式)

    用于改变作用范围的

    @ Scope *******作用:指定bean的作用范围 *******使用地方:类上/方法上 *******属性: ************value:范围的取值。常用取值:singleton—单例,prototype多例(默认是单例)

    AccountServiceImpl .java:

    @Scope("prototype") @Component(value = "accountSer") public class AccountServiceImpl implements IAccountService { @Autowired @Qualifier("accountDao1") private IAccountDao accountDao; public AccountServiceImpl() { System.out.println("对象创建了"); } public void saveAccount(){ accountDao.saveAccount(); } }

    AccountDaoImpl .java:

    @Repository("accountDao1") public class AccountDaoImpl implements IAccountDao{ public void saveAccount(){ System.out.println("保存了账户1111111111111"); } }

    client.java:

    //1.获取核心容器对象 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.根据id获取Bean对象 IAccountService as = (IAccountService)ac.getBean("accountSer"); IAccountService as2 = (IAccountService)ac.getBean("accountSer"); System.out.println(as == as2);

    运行结果:

    案例分析

    基于xml的spring的IOC案例

    AccountServiceImpl .java:

    public class AccountServiceImpl implements IAccountService{ private IAccountDao accountDao; public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao; } }

    AccountDaoImpl .java:

    public class AccountDaoImpl implements IAccountDao { private QueryRunner runner; public void setRunner(QueryRunner runner) { this.runner = runner; } }

    bean.xml:

    <!-- 配置Service --> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <!-- 注入dao --> <property name="accountDao" ref="accountDao"></property> </bean> <!--配置Dao对象--> <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"> <!-- 注入QueryRunner --> <property name="runner" ref="runner"></property> </bean> <!--配置QueryRunner--> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> <!--注入数据源--> <constructor-arg name="ds" ref="dataSource"></constructor-arg> </bean> <!-- 配置数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--连接数据库的必备信息--> <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mydb3?characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=GMT%2B8"></property> <property name="user" value="root"></property> <property name="password" value="636895"></property> </bean>

    分析:创建业务层service对象,往其中注入持久层对象dao,创建持久层对象,往其中注入QueryRunner对象,创建QueryRunner对象,因为它是单例,防止多个dao进行访问,产生线程安全问题,设置成多例模式,往其中注入数据源对象dataSource,创建数据源对象,往其中注入数据库连接信息。

    如下图:

    基于注解的Spring的IOC案例

    使用的注解(重点)

    @Configuration ********作用:指定当前类是一个配置类,相当于bean.xml ********细节:当该类的字节码文件,作为AnnotationConfigApplicationContext创建容器的参数时,该注解可以不写。因为它==使用了@Configuration ********属性:proxyBeanMethods,默认为true,保证依赖的组件始终是单实例的

    有如下2种方式:

    方式一:使用字节码文件的方式,SpringConfiguration作为主配置类,JdbcConfig作为次配置类,全部使用字节码文件作为参数,传入到AnnotationConfigApplicationContext创建对象中去,spring会自动识别他们是一个配置类。 SpringConfiguration.java:

    //@Configuration @ComponentScan("com.itheima") public class SpringConfiguration {}

    JdbcConfig.java:

    public class JdbcConfig { @Bean(name="runner") @Scope("prototype") public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){ return new QueryRunner(dataSource); } @Bean(name="ds2") public DataSource createDataSource(){ try { ComboPooledDataSource ds = new ComboPooledDataSource(); ds.setDriverClass(driver); ds.setJdbcUrl(url); ds.setUser(username); ds.setPassword(password); return ds; }catch (Exception e){ throw new RuntimeException(e); } } }

    测试类:

    ApplicationContext ac=new AnnotationConfigApplicationContext(SpringConfiguration.class,JdbcConfig.class);

    方式二:使用扫描包的方式,这时JdbcConfig使用@Configuration注解,标志位它是一个配置类,才会对该类下的注解进行扫描,否则,无法扫描该类下的注解。 SpringConfiguration.java:

    //@Configuration @ComponentScan({"com.itheima","config"}) public class SpringConfiguration {}

    config包下 JdbcConfig.java:

    @Configuration public class JdbcConfig {}

    测试类:

    ApplicationContext ac=new AnnotationConfigApplicationContext(SpringConfiguration.class); ComponentScan ********作用:指定spring在创建容器时要扫描的包 ********属性: *************value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包。 我们使用此注解就等同于在xml中配置了: <context:component-scan base-package=“com.itheima”></context:component-scan>Bean ******作用:把当前方法的返回值作为bean对象,并存入spring的ioc容器中 ******属性: ********** name:等于bean的id。当不写时,默认值是当前方法的名称 ******细节: ***********当使用@Bean注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象。查找的方式和Autowired注解的作用是一样的 没有注入DataSource对象到Spring的Ioc的容器中,所以报错: 多个类型注入时: 匹配原则:当注入的数据类型和容器中多个bean对象类型匹配时,使用变量名和bean对象中id进行匹配。 使用@Qualifier注解解决: @Qulifier注解作为方法参数使用: public class JdbcConfig { /** * 用于创建一个QueryRunner对象 * @param dataSource * @return */ @Bean(name="runner") @Scope("prototype") public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){ return new QueryRunner(dataSource); } /** * 创建数据源对象 * @return */ @Bean(name="ds2") public DataSource createDataSource(){ try { ComboPooledDataSource ds = new ComboPooledDataSource(); ds.setDriverClass(driver); ds.setJdbcUrl(url); ds.setUser(username); ds.setPassword(password); return ds; }catch (Exception e){ throw new RuntimeException(e); } } @Bean(name="ds1") public DataSource createDataSource1(){ try { ComboPooledDataSource ds = new ComboPooledDataSource(); ds.setDriverClass(driver); ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy02"); ds.setUser(username); ds.setPassword(password); return ds; }catch (Exception e){ throw new RuntimeException(e); } } @Import ******作用:导入其他配置类的字节码文件,将该类的实例注入springIOC核心容器中,且bean的id就是该类的全限定类名。当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类 ****** 属性: ************value:指定其他配置类的字节码文件。

    既不想加入扫描包,也不想加入@Configuration注解,可以使用@Import注解: SpringConfiguration.java:

    //@Configuration @ComponentScan("com.itheima") @Import(JdbcConfig.class) public class SpringConfiguration {}

    config包下 JdbcConfig.java:

    public class JdbcConfig {}

    测试类:

    ApplicationContext ac=new AnnotationConfigApplicationContext(SpringConfiguration.class);

    @PropertySource ********属性: **************value:指定properties配置文件的位置 ************* 关键字:classpath,表示类路径下

    @ImportResource 作用地方:作用在配置类上 作用:导入spring的配置文件(如:bean.xml)让springboot加载spring的xml配置文件。 属性:locations表示xml配置文件路径,关键字classpth表示在当前类路径下

    @ImportResource(locations={"classpath:bean.xml"}) public class MyConfig{ }

    完整案例: AccountDaoImpl.java:

    @Repository("accountDao") public class AccountDaoImpl implements IAccountDao { @Autowired private QueryRunner runner; @Override public List<Account> findAllAccount() { try{ return runner.query("select * from account",new BeanListHandler<Account>(Account.class)); }catch (Exception e) { throw new RuntimeException(e); } } @Override public Account findAccountById(Integer accountId) { try{ return runner.query("select * from account where id = ? ",new BeanHandler<Account>(Account.class),accountId); }catch (Exception e) { throw new RuntimeException(e); } } @Override public void saveAccount(Account account) { try{ runner.update("insert into account(name,money)values(?,?)",account.getName(),account.getMoney()); }catch (Exception e) { throw new RuntimeException(e); } } @Override public void updateAccount(Account account) { try{ runner.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId()); }catch (Exception e) { throw new RuntimeException(e); } } @Override public void deleteAccount(Integer accountId) { try{ runner.update("delete from account where id=?",accountId); }catch (Exception e) { throw new RuntimeException(e); } } }

    AccountServiceImpl.java:

    @Service("accountService") public class AccountServiceImpl implements IAccountService{ @Autowired private IAccountDao accountDao; @Override public List<Account> findAllAccount() { return accountDao.findAllAccount(); } @Override public Account findAccountById(Integer accountId) { return accountDao.findAccountById(accountId); } @Override public void saveAccount(Account account) { accountDao.saveAccount(account); } @Override public void updateAccount(Account account) { accountDao.updateAccount(account); } @Override public void deleteAccount(Integer acccountId) { accountDao.deleteAccount(acccountId); } }

    jdbcConfig.properties:

    #key可以随便设置,value有规定写法 jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/eesy jdbc.username=root jdbc.password=1234

    SpringConfiguration.java:

    @ComponentScan("com.itheima") @Import(JdbcConfig.class) @PropertySource("classpath:jdbcConfig.properties") public class SpringConfiguration {}

    JdbcConfig.java:

    public class JdbcConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; /** * 用于创建一个QueryRunner对象 * @param dataSource * @return */ @Bean(name="runner") @Scope("prototype") public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){ return new QueryRunner(dataSource); } /** * 创建数据源对象 * @return */ @Bean(name="ds2") public DataSource createDataSource(){ try { ComboPooledDataSource ds = new ComboPooledDataSource(); ds.setDriverClass(driver); ds.setJdbcUrl(url); ds.setUser(username); ds.setPassword(password); return ds; }catch (Exception e){ throw new RuntimeException(e); } } @Bean(name="ds1") public DataSource createDataSource1(){ try { ComboPooledDataSource ds = new ComboPooledDataSource(); ds.setDriverClass(driver); ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy02"); ds.setUser(username); ds.setPassword(password); return ds; }catch (Exception e){ throw new RuntimeException(e); } } }

    测试类AccountServiceTest.java:

    @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfiguration.class) public class AccountServiceTest { @Autowired private IAccountService as = null; @Test public void testFindAll() { //3.执行方法 List<Account> accounts = as.findAllAccount(); for(Account account : accounts){ System.out.println(account); } } @Test public void testFindOne() { //3.执行方法 Account account = as.findAccountById(1); System.out.println(account); } @Test public void testSave() { Account account = new Account(); account.setName("test anno"); account.setMoney(12345f); //3.执行方法 as.saveAccount(account); } @Test public void testUpdate() { //3.执行方法 Account account = as.findAccountById(4); account.setMoney(23456f); as.updateAccount(account); } @Test public void testDelete() { //3.执行方法 as.deleteAccount(4); } }

    spring整合junit单元测试(重点)

    1.导入spring整合junit的依赖:

    <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> </dependency>

    2.使用@RunWith(SpringJUnit4ClassRunner.class) ***********作用:创建spring的IOC核心容器 3.使用@ContextConfiguration ***********作用:告知spring,spring的ioc核心容器创建的是基于xml配置文件还是注解类,并且说明位置 ***********属性: ******************locations:指定xml配置文件的位置,加上classpath关键字,表示在类路径下 *******************classes:指定注解类所在的位置 4.细节:当我们使用spring 5.x版本的时候,要求junit的依赖必须是4.12及以上

    AccountServiceTest.java:

    @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfiguration.class) public class AccountServiceTest { @Autowired private IAccountService as = null; }

    AOP(aspect oriented programming)面向切面编程

    spring基于XML的AOP配置

    步骤: 1.把通知Bean也交给spring来管理 2.使用aop:config标签开始AOP的配置 3.使用aop:aspect标签开始切面配置 *********id属性:给切面提供一个唯一标识 *********ref属性:指定通知类bean的Id。 4.在aop:aspect标签的内部使用对应标签来配置通知的类型 **我们现在示例是让printLog方法在切入点方法执行之前执行:所以是前置通知 *******aop:before:表示配置前置通知 *********************method属性:指定Logger类中哪个方法是前置通知 *********************pointcut属性:指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强

    切入点表达式的写法: *********关键字:execution(表达式) *********表达式:访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表) *********标准的表达式写法: public void com.itheima.service.impl.AccountServiceImpl.saveAccount()

    完整示例: AccountServiceImpl.java:

    public class AccountServiceImpl implements IAccountService{ @Override public void saveAccount() { System.out.println("执行了保存"); } @Override public void updateAccount(int i) { System.out.println("执行了更新"+i); } @Override public int deleteAccount() { System.out.println("执行了删除"); return 0; } }

    Logger.java:

    /** * 用于记录日志的工具类,它里面提供了公共的代码 */ public class Logger { /** * 用于打印日志:计划让其在切入点方法执行之前执行(切入点方法就是业务层方法) */ public void printLog(){ System.out.println("Logger类中的pringLog方法开始记录日志了。。。"); } }

    bean.xml:

    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean> <!-- 配置Logger类 --> <bean id="logger" class="com.itheima.utils.Logger"></bean> <!--配置AOP--> <aop:config> <!--配置切面 --> <aop:aspect id="logAdvice" ref="logger"> <!-- 配置通知的类型,并且建立通知方法和切入点方法的关联--> <aop:before method="printLog" pointcut="execution(public void com.itheima.service.impl.AccountServiceImpl.saveAccount())"></aop:before> </aop:aspect> </aop:config>

    测试类:

    public static void main(String[] args) { //1.获取容器 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.获取对象 IAccountService as = (IAccountService)ac.getBean("accountService"); //3.执行方法 as.saveAccount(); }

    运行结果:(在业务层之前执行了) 切入点表达式的多种写法:

    访问修饰符可以省略返回值可以使用通配符,表示任意返回值包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*。另外包名也可以使用..表示当前包及其子包类名和方法名都可以使用*来代替参数列表: 基本类型直接写名称 如: int 引用类型写包名.类名的方式 如:java.lang.String 可以使用..表示有无参数均可

    实际开发中切入点表达式的通常写法: 切到业务层实现类下的所有方法: *com.itheima.service.impl.*.*(..)

    对业务成所有方法进行了增强: bean.xml:

    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean> <!-- 配置Logger类 --> <bean id="logger" class="com.itheima.utils.Logger"></bean> <!--配置AOP--> <aop:config> <!--配置切面 --> <aop:aspect id="logAdvice" ref="logger"> <!-- 配置通知的类型,并且建立通知方法和切入点方法的关联--> <aop:before method="printLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:before> </aop:aspect> </aop:config>

    测试类:

    public static void main(String[] args) { //1.获取容器 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.获取对象 IAccountService as = (IAccountService)ac.getBean("accountService"); //3.执行方法 as.saveAccount(); }

    运行结果:

    四种通知类型

    基于xml

    AccountServiceImpl.java:

    public class AccountServiceImpl implements IAccountService{ @Override public void saveAccount() { System.out.println("执行了保存"); // int i=1/0; } }

    Logger.java:

    public class Logger { /** * 前置通知 */ public void beforePrintLog(){ System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。"); } /** * 后置通知 */ public void afterReturningPrintLog(){ System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。"); } /** * 异常通知 */ public void afterThrowingPrintLog(){ System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。"); } /** * 最终通知 */ public void afterPrintLog(){ System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。"); } }

    bean.xml:

    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean> <!-- 配置Logger类 --> <bean id="logger" class="com.itheima.utils.Logger"></bean> <!--配置AOP--> <aop:config> <!--配置切面 --> <aop:aspect id="logAdvice" ref="logger"> <!-- 配置前置通知:在切入点方法执行之前执行 <aop:before method="beforePrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))" ></aop:before>--> <!-- 配置后置通知:在切入点方法正常执行之后值。后置通知和异常通知永远只能执行一个 <aop:after-returning method="afterReturningPrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:after-returning>--> <!-- 配置异常通知:在切入点方法执行产生异常之后执行。后置通知和异常通知永远只能执行一个 <aop:after-throwing method="afterThrowingPrintLog" ppointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:after-throwing>--> <!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行 <aop:after method="afterPrintLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:after>--> </aop:aspect> </aop:config>

    测试类:

    public static void main(String[] args) { //1.获取容器 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.获取对象 IAccountService as = (IAccountService)ac.getBean("accountService"); //3.执行方法 as.saveAccount(); }

    运行结果:

    xml的aop事务控制(案例)

    AccountServiceImpl.java:

    public class AccountServiceImpl implements IAccountService{ private IAccountDao accountDao; public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao; } @Override public Account findAccountById(Integer accountId) { return accountDao.findAccountById(accountId); } @Override public void transfer(String sourceName, String targetName, Float money) { System.out.println("transfer...."); //2.1根据名称查询转出账户 Account source = accountDao.findAccountByName(sourceName); //2.2根据名称查询转入账户 Account target = accountDao.findAccountByName(targetName); //2.3转出账户减钱 source.setMoney(source.getMoney()-money); //2.4转入账户加钱 target.setMoney(target.getMoney()+money); //2.5更新转出账户 accountDao.updateAccount(source); // int i=1/0; //2.6更新转入账户 accountDao.updateAccount(target); } }

    AccountDaoImpl.java:

    public class AccountDaoImpl implements IAccountDao { private QueryRunner runner; private ConnectionUtils connectionUtils; public void setRunner(QueryRunner runner) { this.runner = runner; } public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } @Override public List<Account> findAllAccount() { try{ return runner.query(connectionUtils.getThreadConnection(),"select * from acount",new BeanListHandler<Account>(Account.class)); }catch (Exception e) { throw new RuntimeException(e); } } @Override public Account findAccountById(Integer accountId) { try{ return runner.query(connectionUtils.getThreadConnection(),"select * from acount where id = ? ",new BeanHandler<Account>(Account.class),accountId); }catch (Exception e) { throw new RuntimeException(e); } } @Override public void saveAccount(Account account) { try{ runner.update(connectionUtils.getThreadConnection(),"insert into acount(name,balance)values(?,?)",account.getName(),account.getMoney()); }catch (Exception e) { throw new RuntimeException(e); } } @Override public void updateAccount(Account account) { try{ runner.update(connectionUtils.getThreadConnection(),"update acount set name=?,balance=? where id=?",account.getName(),account.getMoney(),account.getId()); }catch (Exception e) { throw new RuntimeException(e); } } @Override public void deleteAccount(Integer accountId) { try{ runner.update(connectionUtils.getThreadConnection(),"delete from acount where id=?",accountId); }catch (Exception e) { throw new RuntimeException(e); } } @Override public Account findAccountByName(String accountName) { try{ List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from acount where name = ? ",new BeanListHandler<Account>(Account.class),accountName); if(accounts == null || accounts.size() == 0){ return null; } if(accounts.size() > 1){ throw new RuntimeException("结果集不唯一,数据有问题"); } return accounts.get(0); }catch (Exception e) { throw new RuntimeException(e); } } }

    TransactionManager.java:

    public class TransactionManager { private ConnectionUtils connectionUtils; public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } /** * 开启事务 */ public void beginTransaction(){ try { connectionUtils.getThreadConnection().setAutoCommit(false); }catch (Exception e){ e.printStackTrace(); } } /** * 提交事务 */ public void commit(){ try { connectionUtils.getThreadConnection().commit(); }catch (Exception e){ e.printStackTrace(); } } /** * 回滚事务 */ public void rollback(){ try { connectionUtils.getThreadConnection().rollback(); }catch (Exception e){ e.printStackTrace(); } } /** * 释放连接 */ public void release(){ try { connectionUtils.getThreadConnection().close();//还回连接池中 connectionUtils.removeConnection(); }catch (Exception e){ e.printStackTrace(); } } }

    ConnectionUtils.java:

    public class ConnectionUtils { private ThreadLocal<Connection> tl = new ThreadLocal<Connection>(); private DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * 获取当前线程上的连接 * @return */ public Connection getThreadConnection() { try{ //1.先从ThreadLocal上获取 Connection conn = tl.get(); //2.判断当前线程上是否有连接 if (conn == null) { //3.从数据源中获取一个连接,并且存入ThreadLocal中 conn = dataSource.getConnection(); tl.set(conn); } //4.返回当前线程上的连接 return conn; }catch (Exception e){ throw new RuntimeException(e); } } /** * 把连接和线程解绑 */ public void removeConnection(){ tl.remove(); } }

    bean.xml:

    <!-- 配置Service --> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"> <!-- 注入dao --> <property name="accountDao" ref="accountDao"></property> </bean> <!--配置Dao对象--> <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"> <!-- 注入QueryRunner --> <property name="runner" ref="runner"></property> <!-- 注入ConnectionUtils --> <property name="connectionUtils" ref="connectionUtils"></property> </bean> <!--配置QueryRunner--> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean> <!-- 配置数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--连接数据库的必备信息--> <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mydb3?serverTimezone=GMT%2B8&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false"></property> <property name="user" value="root"></property> <property name="password" value="636895"></property> </bean> <!-- 配置Connection的工具类 ConnectionUtils --> <bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils"> <!-- 注入数据源--> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置事务管理器--> <bean id="txManager" class="com.itheima.utils.TransactionManager"> <!-- 注入ConnectionUtils --> <property name="connectionUtils" ref="connectionUtils"></property> </bean> <!--配置aop--> <aop:config> <!--配置通用切入点表达式--> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut> <aop:aspect id="txAdvice" ref="txManager"> <!--配置前置通知:开启事务--> <aop:before method="beginTransaction" pointcut-ref="pt1"></aop:before> <!--配置后置通知:提交事务--> <aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning> <!--配置异常通知:回滚事务--> <aop:after-throwing method="rollback" pointcut-ref="pt1"></aop:after-throwing> <!--配置最终通知:释放连接--> <aop:after method="release" pointcut-ref="pt1"></aop:after> </aop:aspect> </aop:config>

    测试类:

    @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:bean.xml") public class AccountServiceTest { @Autowired private IAccountService as; @Test public void testTransfer(){ as.transfer("张无忌","谢逊",100f); } }

    运行结果: 注释 int i=1/0; 转账失败,事务进行回滚 放开注释,成功转账

    基于xml配置环绕通知

    AccountServiceImpl.java:

    public class AccountServiceImpl implements IAccountService{ @Override public void saveAccount() { System.out.println("执行了保存"); // int i=1/0; } }

    bean.xml:

    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean> <!-- 配置Logger类 --> <bean id="logger" class="com.itheima.utils.Logger"></bean> <!--配置AOP--> <aop:config> <!---配置通用切入点表达式-> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut> <!--配置切面 --> <aop:aspect id="logAdvice" ref="logger"> <!-- 配置环绕通知 详细的注释请看Logger类中--> <aop:around method="aroundPringLog" pointcut-ref="pt1"></aop:around> </aop:aspect> </aop:config>

    Logger.java: Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法,因此切入点方法和通知方法都会执行。

    public class Logger { public Object aroundPringLog(ProceedingJoinPoint pjp){ Object rtValue = null; try{ Object[] args = pjp.getArgs();//得到方法执行所需的参数 System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置"); rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法) System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置"); return rtValue; }catch (Throwable t){ System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常"); throw new RuntimeException(t); }finally { System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终"); } } }

    测试类:

    public class AOPTest { public static void main(String[] args) { //1.获取容器 ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); //2.获取对象 IAccountService as = (IAccountService)ac.getBean("accountService"); //3.执行方法 as.saveAccount(); }

    运行结果:

    基于注解配置环绕通知

    注解(重点)

    @Aspect 作用:表示当前类是一个切面类 使用地方:当前类上@EnableAspectJAutoProxy 作用:开启spring对注解aop的支持@Pointcut 作用:联结切入点方法和通知方法 使用地方:方法上 关键字:execution(表达式)@Before 作用:前置通知 使用地方:方法上@AfterReturning 作用:后置通知 使用地方:方法上@AfterThrowing 作用:异常通知 使用地方:方法上@After 作用:最终通知 使用地方:方法上@Around 作用:环绕通知 使用地方:方法上

    AccountServiceImpl.java:

    @Service("accountService") public class AccountServiceImpl implements IAccountService{ @Override public void saveAccount() { System.out.println("执行了保存"); // int i=1/0; } }

    通知类Logger.java:

    @Component("logger") @Aspect//表示当前类是一个切面类 public class Logger { @Pointcut("execution(* com.itheima.service.impl.*.*(..))") private void pt1(){} @Around("pt1()") public Object aroundPringLog(ProceedingJoinPoint pjp){ Object rtValue = null; try{ Object[] args = pjp.getArgs();//得到方法执行所需的参数 System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置"); rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法) System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置"); return rtValue; }catch (Throwable t){ System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常"); throw new RuntimeException(t); }finally { System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终"); } } }

    配置类SpringConfig.java:

    //@Configuration @ComponentScan("com.itheima") @EnableAspectJAutoProxy public class SpringConfig { }

    测试类:

    public static void main(String[] args) { //1.获取容器 // ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml"); ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class); //2.获取对象 IAccountService as =ac.getBean("accountService",IAccountService.class); System.out.println(as); //3.执行方法 as.saveAccount(); }

    运行结果:

    Spring中的JdbcTemplate

    JdbcTemplate的作用

    spring提供和数据库进行交互,实现对表的CRUD操作

    如何创建对象

    常用方法

    事务控制

    基于xml的声明式事务控制

    步骤:

    配置事务管理器配置事务通知 使用tx:advice标签配置事务通知 属性: id:给事务通知起一个唯一标识 transaction-manager:给事务通知提供一个事务管理器引用配置AOP中的通用切入点表达式建立事务通知和切入点表达式的对应关系配置事务的属性 是在事务通知tx:advice标签的内部 使用tx:attributes标签,在其内部使用tx:method标签 属性: name:对哪个方法进行 isolation:指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。 propagation:指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。 read-only:指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。 timeout:指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。 rollback-for:指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有给值,表示任何异常都回滚。 no-rollback-for:指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有给值,表示任何异常都回滚。

    案例: AccountServiceImpl.java:

    public class AccountServiceImpl implements IAccountService{ private IAccountDao accountDao; public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao; } @Override public Account findAccountById(Integer accountId) { return accountDao.findAccountById(accountId); } @Override public void transfer(String sourceName, String targetName, Float money) { System.out.println("transfer...."); //2.1根据名称查询转出账户 Account source = accountDao.findAccountByName(sourceName); //2.2根据名称查询转入账户 Account target = accountDao.findAccountByName(targetName); //2.3转出账户减钱 source.setMoney(source.getMoney()-money); //2.4转入账户加钱 target.setMoney(target.getMoney()+money); //2.5更新转出账户 accountDao.updateAccount(source); int i=1/0; //2.6更新转入账户 accountDao.updateAccount(target); } }

    bean.xml:

    <!-- 1.配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 2.配置事务的通知--> <tx:advice id="txAdvice" transaction-manager="transactionManager"> <!-- 5.配置事务的属性 isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。 propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。 read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。 timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。 rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。 no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。 --> <tx:attributes> <!--*表示所有方法--> <tx:method name="*" propagation="REQUIRED" read-only="false"/> <!--find*表示所有查询方法 看自己怎么写--> <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method> </tx:attributes> </tx:advice> <!-- 配置aop--> <aop:config> <!--3. 配置切入点表达式--> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut> <!--4.建立切入点表达式和事务通知的对应关系 --> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor> </aop:config>

    AccountDaoImpl.java:

    public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao { @Override public Account findAccountById(Integer accountId) { List<Account> accounts = super.getJdbcTemplate().query("select * from acount where id = ?",new BeanPropertyRowMapper<Account>(Account.class),accountId); return accounts.isEmpty()?null:accounts.get(0); } @Override public Account findAccountByName(String accountName) { List<Account> accounts = super.getJdbcTemplate().query("select * from acount where name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName); if(accounts.isEmpty()){ return null; } if(accounts.size()>1){ throw new RuntimeException("结果集不唯一"); } return accounts.get(0); } @Override public void updateAccount(Account account) { super.getJdbcTemplate().update("update acount set name=?,balance=? where id=?",account.getName(),account.getMoney(),account.getId()); } }

    测试类:

    @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:bean.xml") public class AccountServiceTest { @Autowired private IAccountService as; @Test public void testTransfer(){ as.transfer("张无忌","谢逊",100f); } }

    运行结果:

    基于纯注解的声明式事务控制

    配置事务管理器开启spring对注解事务的支持 @EnableTransactionManagement在需要事务支持的地方使用@Transactional注解

    AccountServiceImpl.java:

    /** * * 事务控制应该都是在业务层 */ @Service("accountService") @Transactional(propagation= Propagation.SUPPORTS,readOnly=true)//只读型事务的配置,只能查询 public class AccountServiceImpl implements IAccountService{ @Autowired private IAccountDao accountDao; @Override public Account findAccountById(Integer accountId) { return accountDao.findAccountById(accountId); } //上面进行只读型配置,但是这里需要的是读写型事务配置,所以继续进行配置 @Transactional(propagation= Propagation.REQUIRED,readOnly=false) @Override public void transfer(String sourceName, String targetName, Float money) { System.out.println("transfer...."); //2.1根据名称查询转出账户 Account source = accountDao.findAccountByName(sourceName); //2.2根据名称查询转入账户 Account target = accountDao.findAccountByName(targetName); //2.3转出账户减钱 source.setMoney(source.getMoney()-money); //2.4转入账户加钱 target.setMoney(target.getMoney()+money); //2.5更新转出账户 accountDao.updateAccount(source); int i=1/0; //2.6更新转入账户 accountDao.updateAccount(target); } }

    AccountDaoImpl.java:

    @Repository("accountDao") public class AccountDaoImpl implements IAccountDao { @Autowired private JdbcTemplate jdbcTemplate; @Override public Account findAccountById(Integer accountId) { List<Account> accounts = jdbcTemplate.query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),accountId); return accounts.isEmpty()?null:accounts.get(0); } @Override public Account findAccountByName(String accountName) { List<Account> accounts = jdbcTemplate.query("select * from account where name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName); if(accounts.isEmpty()){ return null; } if(accounts.size()>1){ throw new RuntimeException("结果集不唯一"); } return accounts.get(0); } @Override public void updateAccount(Account account) { jdbcTemplate.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId()); } }

    SpringConfiguration.java:

    /** * spring的配置类,相当于bean.xml */ @Configuration @ComponentScan("com.itheima") @Import({JdbcConfig.class,TransactionConfig.class}) @PropertySource("jdbcConfig.properties") @EnableTransactionManagement //开启spring对注解事务的支持 public class SpringConfiguration { }

    JdbcConfig.java:

    /** * 和连接数据库相关的配置类 */ public class JdbcConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; /** * 创建JdbcTemplate * @param dataSource * @return */ @Bean(name="jdbcTemplate") public JdbcTemplate createJdbcTemplate(DataSource dataSource){ return new JdbcTemplate(dataSource); } /** * 创建数据源对象 * @return */ @Bean(name="dataSource") public DataSource createDataSource(){ DriverManagerDataSource ds = new DriverManagerDataSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUsername(username); ds.setPassword(password); return ds; } }

    TransactionConfig.java:

    /** * 和事务相关的配置类 */ public class TransactionConfig { /** * 用于创建事务管理器对象 * @param dataSource * @return */ @Bean(name="transactionManager") public PlatformTransactionManager createTransactionManager(DataSource dataSource){ return new DataSourceTransactionManager(dataSource); } }

    jdbcConfig.properties:

    jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/eesy jdbc.username=root jdbc.password=1234

    测试类:

    @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes= SpringConfiguration.class) public class AccountServiceTest { @Autowired private IAccountService as; @Test public void testTransfer(){ as.transfer("aaa","bbb",100f); } }

    运行结果:

    spring的新特性

    Processed: 0.015, SQL: 8