文章目录
1 IoC综合案例(CRUD)—— 纯xml开发
开发流程概述
1 导入jar包依赖
2 创建pojo
3 创建service业务层接口
4 创建service业务层的接口实现类
依赖注入,创建dao层对象
对象引出创建dao层接口,方法与service接口方法完全一样
service实现类的方法中利用dao对象调用dao实现类的对象
5 创建Sql配置文件
复制接口中的方法,依次对应每个方法写出增删改查的sql语句
6 创建jdbc.properties连接数据库参数
7 创建spring核心配置文件ApplicationContext.xml
编写Dao层
加载properties配置文件
配置Druid数据源: bean对象,bean容器中
配置SqlSessionFactory的对象: bean对象,bean容器中
数据源(ref用到Druid数据源的bean对象)
pojo别名映射
配置dao层动态代理对象生成的扫描器
指定扫描的dao层路径
编写Service层
依赖注入:注入dao对象
说明:上面配置dao层,已经扫描生成动态代理对象,已经在ioc容器中存放好
对象名称默认是接口名称,首字母小写
8 编写junit测试类
2 Spring注解开发概述
2.1 Bean标签和注解的对应(定义Bean的对象)
-
bean标签对应注解@Component
-
使用方法:加到类上,创建该类的对象,并放到ioc容器中
-
- 注解属性value:bean标签的id属性
- 不指定value属性,默认就是类名,首字母小写
- 该注解衍生出了三个注解,@Controller,@Service,@Repository,用法和@Componet一致,为了更加清晰的提现层的概念。
-
bean标签属性scope对应注解@Scope
- 注解属性value:singleton,prototype
-
bean标签属性init-method对应注解@PostConstruct
-
bean标签属性destroy-method对应注解@PreDestroy
tips:Spring只加载xml,不会自动扫描注解,xml中需要开启注解扫描
-
service层
public interface AccountService { //模拟保存账户 void save(); }
//@Component("accountService") @Service("accountService") @Scope("prototype") public class AccountServiceImpl implements AccountService { private AccountDao accountDao; public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } @Override public void saveAccount() { accountDao.saveAccount(); } }
-
dao层
public interface AccountDao { //模拟保存账户 void save(); }
//@Component("accountDao") @Repository("accountDao") public class AccountDaoImpl implements AccountDao { @Override public void saveAccount() { System.out.println("保存了账户"); } private void init() { System.out.println("AccountDao对象初始化"); } private void destroy() { System.out.println("AccountDao对象 销毁了"); } }
-
添加applicationContext配置文件命名空间
<?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" > <!-- 开启spring的注解扫描,扫描包中类的注解--> <context:component-scan base-package="com.itheima"></context:component-scan> </beans>
-
测试注解
@Test public void testIOC(){ ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); AccountService accountService = context.getBean(AccountService.class); AccountDao accountDao = context.getBean(AccountDao.class); System.out.println(accountService); System.out.println(accountDao); context.close(); }
2.2 依赖注入注解(对象初始化)
-
@Autowired注解(Spring框架提供)
按照类型注入,如果无法确定唯一类型(接口有多个实现类),需要配合注解@Qualifier的使用
-
@Qualifier(“id”) 注解(Spring框架提供)
- 按照id注入
-
@Resource注解(JDK提供)
- 注解属性name:配置类的id
@Resource(name="accountDao") private AccountDao accountDao;
代码演示
- 业务层
@Service("accountService")
@Scope("singleton")
public class AccountServiceImpl implements AccountService {
@Autowired
//@Qualifier("accountDao2")
//@Resource(name = "accountDao2")
private AccountDao accountDao;
@Override
public void saveAccount(Account account) {
accountDao.saveAccount(account);
}
}
- dao层
package com.itheima.dao.impl;
/**
* 账户dao实现类
*/
@Component("accountDao")
//@Repository("accountDao")
@Scope("singleton")
public class AccountDaoImpl implements AccountDao {
@Override
public void saveAccount(Account account) {
System.out.println("模拟转账");
}
@PostConstruct()
public void init(){
System.out.println("AccountDaoImpl 初始化...");
}
@PreDestroy
public void destroy(){
System.out.println("AccountDaoImpl 销毁...");
}
}
2.3 Spring对Junit的支持
- junit运行的时候底层使用了Runner对象,有一个默认使用的Runner对象。
- Spring对junit的支持,其实是自己实现了一个Runner对象(按照junit runner的要求实现)
- Spring对junit的支持的体现
- 好处一:配置完之后,不需要我们手动的启动Spring
- 好处二:可以在junit测试类中使用@AutoWired等方式注入对象,直接对其进行调用测试
- 使用步骤
- 引入spring-test.jar
- 配置测试类
//Spring框架中的Runner对象, 替换Junit中的Runner对象
@RunWith(SpringJUnit4ClassRunner.class)
//框架启动入口, xml配置文件启动(2选1)
@ContextConfiguration(locations = "classpath:beans.xml")
//框架启动入口, 注解方式配置文件启动(2选1)
//@ContextConfiguration(classes = SpringConfig.class)
-
代码实现
- pom.xml
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.1.9.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.1.9.RELEASE</version> </dependency> </dependencies>
- 测试类
package com.itheima; //Spring框架中的Runner对象, 替换Junit中的Runner对象 @RunWith(SpringJUnit4ClassRunner.class) //框架启动入口, 注解方式配置文件启动 //@ContextConfiguration(classes = SpringConfig.class); //框架启动入口, xml配置文件启动 @ContextConfiguration(locations = "classpath:ApplicationContext.xml") public class AccountTest { //注入业务层接口 @Autowired private AccountService service; @Test public void testIOC(){ System.out.println("service = " + service); Account account = new Account(); account.setName("小米"); account.setMoney(888.0F); service.saveAccount(account); } }
3 IoC的综合案例(CRUD) —— 半注解半xml(真实开发)
3.1 简述流程
半注解半xml(删除了service层xml配置)
哪些bean对象用xml还是注解来配置:
用第三方的使用xml配置
我们自己编写的使用注解配置
案例:
1 dao层都是第三方Mybatis的使用xml,service层使用注解
2 @Service 依赖注入@Autowired,动态代理机制赋值对象
3.2 开发详解
企业主流的开发方式
注意:往往第三方jar中的对象我们使用xml配置(比如druid数据库连接池、Mybatis的SQLSessionFactory),类似于service层和dao层的实现类,这属于我们自己写的代码,往往会使用注解,这就是半xml半注解的模式。
-
applicationContext.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 https://www.springframework.org/schema/context/spring-context.xsd"> <!--配置dao--> <!--配置properties文件的位置--> <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder> <!--配置数据源--> <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"></property> <property name="url" value="${jdbc.url}"></property> <property name="username" value="${jdbc.username}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!--配置mybatis的SqlSessionFactory工厂--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="druidDataSource"></property> <property name="typeAliasesPackage" value="com.itheima.pojo"></property> </bean> <!--配置创建dao代理实现类的扫描器--> <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.itheima.dao"></property> </bean> <!--配置service--> <!--让Spring开启注解扫描--> <context:component-scan base-package="com.itheima"></context:component-scan> </beans>
-
service层
package com.itheima.service.impl; /** * 账户业务接口实现类 */ @Service("accountService") public class AccountServiceImpl implements AccountService { //依赖注入 @Autowired private AccountDao accountDao; //增删改查方法 无修改, 笔记中代码省略 }
-
测试类
/* 测试类 */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:ApplicationContext.xml"}) public class AccountServiceTest { @Autowired private AccountService accountService; //测试类方法省略 @Test public void findAll() { List<Account> accountList = accountService.findAll(); for (Account account : accountList) { System.out.println("account = " + account); } } }
4 IoC的综合案例(CRUD) —— 纯注解开发(真实开发)
4.1 简述流程
纯注解开发(删除所有的配置文件:service层删除,dao层的配置删除)
1 修改dao层:将sql语句由原来的配置文件写入dao接口对应方法中@Insert @Select等
开发中都是采用注解的形式,不是采用配置文件的方式
2 添加Spring的配置类(加上注解@Configuration表示是Spring的配置类)
@Bean把方法返回值对象放入到SpringIOC容器中
加载properties配置文件
@PropertySource({配置文件1的路径1},{配置文件2的路径2})
配置数据源
以方法的形式体现:返回数据源对象
通过@Bean注解实现将方法返回值加入到Spring容器
配置SQLSessionFactory
以方法的形式体现:返回SqlSessionFactory对象
@Bean注解
@Qualifier配合指定Spring容器中传入哪个DataSource数据源对象,依赖注入
dao扫描器动态代理对象
配置扫描路径
@Bean将对象返回放入到Spring容器中
开启SpringIOC容器的注解扫描:扫描到service层上配置的注解
@ComponentScan({要扫描的包的路径})
3 测试类
@ContextConfiguration({classes={XXX.class}})加载注解形式的java文件
4 后期优化:
将总的Spring配置文件进行按技术拆分子配置文件:
SpringConfig.java: 总配置文件@import两个子配置文件
子配置文件可以不用加@Configuration注解,总配置文件中加就可以了
JDBCConfig.java:和jdbc相关的:配置数据源的
加载外部的properties文件:@PropertySource({配置文件1的路径1},{配置文件2的路径2})
通过@value注解获取properties文件中的值,为成员变量赋值
MyBatisConfig.java:
4.2 相关注解说明
-
@Configuration标识当前类是Spring的一个配置类
-
@ComponentScan替代xml中的
<context:component-scan/>
-
@Import引入其他配置类,被引入的配置类可以不加@Configuration注解
-
@PropertySource:引入外部properties文件,注意加classpath:
-
@Value对成员变量赋值
-
@Bean将一个方法的返回值对象加入到Spring的容器当中管理
-
@Qualifier可以使用在方法参数上,表明对应的形参引入/注入的对象类型
4.3 SpringConfig框架启动配置类
//Spring配置类,框架启动入口
@Configuration
//启动注解扫描
@ComponentScan({"com.itheima"})
public class SpringConfig {
//方法的返回值, 加入到SpringIOC容器中管理
@Bean("druidDataSource")
public DataSource createDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
druidDataSource.setDriverClassName("com.mysql.jdbc.Driver");
druidDataSource.setUrl("jdbc:mysql://localhost:3306/ssm_lx");
druidDataSource.setUsername("root");
druidDataSource.setPassword("root");
return druidDataSource;
}
@Bean("sqlSessionFactory")
public SqlSessionFactoryBean createSqlSessionFactoryBean(@Qualifier("druidDataSource") DataSource ds){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(ds);
sqlSessionFactoryBean.setTypeAliasesPackage("com.itheima.pojo");
return sqlSessionFactoryBean;
}
@Bean("scannerConfigurer")
public MapperScannerConfigurer createMapperScannerConfigurer(){
MapperScannerConfigurer scannerConfigurer = new MapperScannerConfigurer();
scannerConfigurer.setBasePackage("com.itheima.dao");
return scannerConfigurer;
}
}
- 测试
/*
测试类
*/
@RunWith(SpringJUnit4ClassRunner.class)
//框架启动入口, 注解方式配置文件启动(2选1)
@ContextConfiguration(classes = SpringConfig.class)
public class CRUDTest {
@Autowired
private AccountService accountService;
@Test
public void findByName(){
Account account = accountService.findByName("迪丽热巴");
System.out.println("account = " + account);
}
@Test
public void findAll(){
List<Account> accountList = accountService.findAll();
for (Account account : accountList) {
System.out.println("account = " + account);
}
}
}
4.4 注解开发的SpringConfig配置优化
-
SpringConfig框架启动配置类
/* 作为Spring框架的主配置文件 */ @Configuration //开启Spring容器的注解扫描 @ComponentScan({"com.itheima"}) //导入子配置文件 @Import({JDBCConfig.class, MybatisConfig.class}) public class SpringConfig { }
-
JDBCConfig配置类
//用于指定与数据库相关配置的配置文件 @Configuration @PropertySource({"classpath:jdbc.properties"}) public class JDBCConfig { @Value("${jdbc.driverClassName}") private String driverClassName; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; //配置数据源 @Bean("DataSource") public DataSource createDataSource(){ //创建Druid数据源 DruidDataSource druidDataSource = new DruidDataSource(); //配置相关信息(Driver, url, username, password) druidDataSource.setDriverClassName(driverClassName); druidDataSource.setUrl(url); druidDataSource.setUsername(username); druidDataSource.setPassword(password); return druidDataSource; } }
-
Mybatis配置类
//用于配置与Mybatis相关的配置 @Configuration public class MybatisConfig { //配置SqlSessionFactoryBean对象 @Bean("sqlSessionFactory") public SqlSessionFactoryBean createSqlSessionFactoryBean(DataSource ds){ //创建SqlSessionFactoryBean 对象 SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean(); //配置相关信息(数据源, pojo别名映射) sqlSessionFactory.setDataSource(ds); sqlSessionFactory.setTypeAliasesPackage("com.itheima.pojo"); return sqlSessionFactory; } //配置dao的包扫描 @Bean("scannerConfigurer") public MapperScannerConfigurer createMapperScannerConfigurer(){ //创建MapperScannerConfigurer 对象 MapperScannerConfigurer scannerConfigurer = new MapperScannerConfigurer(); //配置相关信息(扫描的dao包, 找到了dao层的接口文件, 找到了SQL映射文件, 生成接口实现类对象并存到Spring容器中) scannerConfigurer.setBasePackage("com.itheima.dao"); return scannerConfigurer; } }
-
测试类
/* 测试类 */ @RunWith(SpringJUnit4ClassRunner.class) //加载的xml配置文件 //@ContextConfiguration(locations = {"classpath:ApplicationContext.xml"}) //加载的注解形式的Spring主配置文件 @ContextConfiguration(classes = {SpringConfig.class}) public class AccountServiceTest { @Autowired private AccountService accountService; //其他方法省略 @Test public void findAll() { List<Account> accountList = accountService.findAll(); for (Account account : accountList) { System.out.println("account = " + account); } } }
5 Spring框架事务的管理
5.1 案例引入
转账案例
1 创建service接口
创建接口方法transfer(name1, name2, money)
2 创建service接口实现类
实现转账方法transfer
获取两个目标账户对象
-money set
+money set
更新数据库表里的金额
在实现转账transfer的过程中,如果出现错误,此时要实现事务回滚错误,保持事务的一致性。
5.2 常规做法
1 开启事务
2 提交事务
3 回滚事务
4 释放连接
service层中对事务的操作添加事务的控制:
这里创建了管理事务的工具类,将事务的处理以及异常捕捉的处理放到了工具类中来优化代码。
public void transfer(String resource, String target, double money)
{
try{
this.transactionManager.begin();
Account resourceAccount = this.accountDao.findByName(resource);
System.out.println("当前线程:" + Thread.currentThread().getName());
Account targetAccount = this.accountDao.findByName(target);
resourceAccount.setMoney(Double.valueOf(resourceAccount.getMoney().doubleValue() - money));
targetAccount.setMoney(Double.valueOf(targetAccount.getMoney().doubleValue() + money));
this.accountDao.update(resourceAccount);
System.out.println("当前线程:" + Thread.currentThread().getName());
int i = 1 / 0;
this.accountDao.update(targetAccount);
this.transactionManager.commit();
} catch (Exception e) {
e.printStackTrace();
this.transactionManager.rollback();
} finally {
this.transactionManager.close();
}
}
5.3 出现的问题
以上会出现事务控制不成功。
如何保证使用同一个连接来做的事务?
不从连接池中去取连接,从当前主线程(绑定的连接对象)获取连接对象。
ThreadLocal解决该问题:在当前线程上绑定局部变量,实现当前线程的任意执行位置获取绑定对象的值
使用案例:service和dao代码交互:获取连接不是在同一个java类中,使用ThreadLocal来绑定连接对象。
5.4 ThreadLocal使用介绍
使用ThreadLocal来绑定一个字符串,使用字符串的时候get能够得到。
5.5 Spring中事务的控制
1 配置数据源
配置Connection对象 createConnection
解决原理:
当前的事务代码Connection对象 与 Mybatis中的SQLSession对象中的Connection对象 不是同一个连接对象
使用ThreadLocal技术, 实现 SQLSession对象 与 事务代码Connection对象 是同一个
通过Spring框架来实现这个操作 Connection对象 是 同一个
@Configuration
@PropertySource({"classpath:jdbc.properties"})
public class JDBCConfig {
@Value("${jdbc.driverClassName}")
private String driverClassName;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
//配置数据源
@Bean("druidDataSource")
public DataSource createDataSource(){
DruidDataSource druidDataSource = new DruidDataSource();
//配置相关信息(驱动, url, 用户名, 密码)
druidDataSource.setDriverClassName(driverClassName);
druidDataSource.setUrl(url);
druidDataSource.setUsername(username);
druidDataSource.setPassword(password);
return druidDataSource;
}
//配置Connection对象
@Bean
public Connection createConnection(DataSource dataSource){
/*
当前的事务代码Connection对象 与 Mybatis中的SQLSession对象中的Connection对象 不是同一个连接对象
使用ThreadLocal技术, 实现 SQLSession对象 与 事务代码Connection对象 是 同一个
通过Spring框架来实现这个操作 Connection对象 是 同一个
*/
//1. 通过Spring框架提供的事务同步管理器, 实现事务代码Connection对象 与 Mybatis中的SQLSession对象中的Connection对象 同一个
//initSynchronization()方法中, 代码底层源码中, 通过 ThreadLocal实现了 当前线程与 Connection对象进行绑定
TransactionSynchronizationManager.initSynchronization();
// Spring框架提供了一个 DataSourceUtils工具类, getConnection()方法,实现了从ThreadLocal 中 绑定的Connection对象
Connection connection = DataSourceUtils.getConnection(dataSource);
return connection;
}
}
2 注入连接对象
//依赖注入
@Autowired
private Connection connection;
管理事务工具类的完整代码:
/*
管理事务的工具类
*/
@Component
public class TransactionManager {
//依赖注入
@Autowired
private Connection connection;
//开启事务
public void begin() {
try {
//当前当前代码执行所在的线程对象
//System.out.println("当前线程:"+Thread.currentThread().getName());
System.out.println("开启事务connection = " + connection);
connection.setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
//提交事务
public void commit() {
try {
//当前当前代码执行所在的线程对象
//System.out.println("当前线程:"+Thread.currentThread().getName());
System.out.println("提交事务connection = " + connection);
connection.commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
//回滚事务
public void rollback() {
try {
//当前当前代码执行所在的线程对象
//System.out.println("当前线程:"+Thread.currentThread().getName());
System.out.println("回滚事务connection = " + connection);
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
//释放连接
public void close() {
try {
//当前当前代码执行所在的线程对象
//System.out.println("当前线程:"+Thread.currentThread().getName());
System.out.println("释放连接connection = " + connection);
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
6 IDEA使用小技巧
配置模板,利用快捷键生成常用的代码,避免最大程度的出错
开发小技巧:
settings中:
配置keyMap快捷键,提示:basic
配置常用代码块模板:template——> code template 新建模板
在我们用的时候new中会出现
配置哪些模板:
Druid Pool连接数据库的配置文件
日志log4j
MyBatis Mapper的映射文件
MyBatis SqlMapConfig的核心配置文件
找Maven Project.xml,将java的编译版本配置项properties放进去
pom.xml中maven.compiler.source 1.8
idea配置还原:
路径位置:当前/用户/IntelliJidea/config和system两个文件夹删了,重新激活idea,代价是模板、快键键都要重新配置
Maven中创建module,不能创建project中创建module,project和module是平行的
学会debug: