Spring框架
今日内容
- 能够使用spring的jdbc的模板
- 能够配置spring的连接池
- 能够使用jdbc模板完成增删改查的操作
- 能够说出两种编写Dao代码的区别
- 能够说出spring的事务管理的方式和常用接口
- 能够理解事务的传播行为
- 能够应用声明式事务
- 能够配置spring配置文件的约束提示
- 够实现spring基于XML的IoC案例
- 能够实现spring基于aop配置的事务控制案例
- 能够应用编程式事务
JdbcTemplate开发dao层程序
由Spring框架给我们提供,Spring提供了很多操作数据源(关系型数据库,二维表格模型,有明确的行和列(mysql\oracle等)、非关系型数据库(redis、mongodb)NoSql、消息队列(activeMq、jms))的小工具
JdbcTemplate操作关系数据库
RedisTemplate操作redis
JmsTemplate操作消息队列
JdbcTemplate类
- 使用方法和QueryRunner基本一致
- 构造方法传递数据源DataSource对象
- API方法
- update(String sql, Object…obj)执行insert,update,delete语句
- queryForObject(String sql,RowMapper mapper,Object…obj)查询返回单个对象
- queryForObject(String sql,Class cla,Object…obj)查询返回单个对象,基本类型及其包装类和字符串
- query(String sql,RowMapper mapper,Object…obj)查询返回集合对象
- RowMapper接口实现类BeanPropertyRowMapper,查询的结果集封装,适用单个对象或者集合
JdbcTemplate类实现account表CRUD
- applicationContext.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
">
<context:component-scan base-package="com.itheima"></context:component-scan>
<!-- 配置数据源,数据库连接池-->
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<!-- 注入DriverManagerDataSource类的属性-->
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/test"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!-- 配置JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!-- 构造方法注入-->
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
</beans>
- dao层
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
//注入JdbcTemplate对象
@Autowired
@Qualifier("jdbcTemplate")
private JdbcTemplate jdbcTemplate ;
//保存账户
@Override
public int saveAccount(Account account) throws SQLException {
String sql = "insert into account values(?,?,?)";
return jdbcTemplate.update(sql,null,account.getName(),account.getMoney());
}
//更新账户
@Override
public int updateAccountById(Account account) throws SQLException {
String sql = "update account set name = ? , money = ? where id = ?";
return jdbcTemplate.update(sql,account.getName(),account.getMoney(),account.getId());
}
//删除账户
@Override
public int deleteAccountByid(int id) throws SQLException {
String sql = "delete from account where id = ? ";
return jdbcTemplate.update(sql,id);
}
//id查询账户
@Override
public Account queryAccountById(int id) throws SQLException {
String sql = "select id,name,money from account where id = ?";
return jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<Account>(Account.class),id);
}
//查询全部账户
@Override
public List<Account> queryAccountByList() throws SQLException {
String sql = "select id,name,money from account";
return jdbcTemplate.query(sql,new BeanPropertyRowMapper<Account>(Account.class));
}
//查询账户总条数
@Override
public int queryAccountByCount() throws SQLException {
String sql = "select count(id) from account";
return jdbcTemplate.queryForObject(sql,int.class);
}
//自定义RowMapper,pojo对象属性和数据表不对应
@Override
public List<Account> queryAccountByAsName() throws SQLException {
String sql = "select id ids,name names,money moneys from account";
return jdbcTemplate.query(sql, new RowMapper<Account>(){
public Account mapRow(ResultSet resultSet,int row)throws SQLException{
Account account = new Account();
account.setId( resultSet.getInt("ids") );
account.setName( resultSet.getString("names"));
account.setMoney( resultSet.getDouble("moneys"));
return account;
}
});
}
}
- service层
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
@Qualifier("accountDao")
private AccountDao accountDao ;
@Override
public int saveAccount(Account account) throws SQLException {
return accountDao.saveAccount(account);
}
@Override
public int updateAccountById(Account account) throws SQLException {
return accountDao.updateAccountById(account);
}
@Override
public int deleteAccountById(int id) throws SQLException {
return accountDao.deleteAccountByid(id);
}
@Override
public Account queryAccountById(int id) throws SQLException {
return accountDao.queryAccountById(id);
}
@Override
public List<Account> queryAccountByList() throws SQLException {
return accountDao.queryAccountByList();
}
@Override
public int queryAccountByCount() throws SQLException {
return accountDao.queryAccountByCount();
}
@Override
public List<Account> queryAccountByAsName() throws SQLException {
return accountDao.queryAccountByAsName();
}
}
- 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class MainTest {
@Autowired
@Qualifier("accountService")
private AccountService service;
@Test
public void testSaveAccount()throws SQLException{
Account account = new Account();
account.setName("老王");
account.setMoney(1200);
service.saveAccount(account);
}
@Test
public void testUpdateAccountById()throws SQLException{
Account account = new Account();
account.setId(7);
account.setName("老王2");
account.setMoney(1220);
service.updateAccountById(account);
}
@Test
public void testDeleteAccountById()throws SQLException{
service.deleteAccountById(7);
}
@Test
public void testQueryAccountById()throws SQLException{
Account account = service.queryAccountById(2);
System.out.println(account);
}
@Test
public void testQueryAccountByCount() throws SQLException{
int count = service.queryAccountByCount();
System.out.println(count);
}
@Test
public void testQueryAccountByList() throws SQLException{
List<Account> accountList = service.queryAccountByList();
if (accountList != null && accountList.size() > 0){
for(Account account : accountList){
System.out.println(account);
}
}
}
@Test
public void testQueryAccountByListAsName() throws SQLException {
List<Account> accountList = service.queryAccountByAsName();
if (accountList != null && accountList.size() > 0){
for(Account account : accountList){
System.out.println(account);
}
}
}
}
继承JdbcDaoSupport类
JdbcDaoSupport类中定义了JdbcTemplate类,我们dao层类直接继承即可,自己无需在声明JdbcTemplate对象,但是不能使用注解方式注入了。
- dao层改进
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
/* @Autowired
@Qualifier("jdbcTemplate")
private JdbcTemplate jdbcTemplate ;*/
@Override
public int saveAccount(Account account) throws SQLException {
String sql = "insert into account values(?,?,?)";
return getJdbcTemplate().update(sql,null,account.getName(),account.getMoney());
}
@Override
public int updateAccountById(Account account) throws SQLException {
String sql = "update account set name = ? , money = ? where id = ?";
return getJdbcTemplate().update(sql,account.getName(),account.getMoney(),account.getId());
}
@Override
public int deleteAccountByid(int id) throws SQLException {
String sql = "delete from account where id = ? ";
return getJdbcTemplate().update(sql,id);
}
@Override
public Account queryAccountById(int id) throws SQLException {
String sql = "select id,name,money from account where id = ?";
return getJdbcTemplate().queryForObject(sql,new BeanPropertyRowMapper<Account>(Account.class),id);
}
@Override
public List<Account> queryAccountByList() throws SQLException {
String sql = "select id,name,money from account";
return getJdbcTemplate().query(sql,new BeanPropertyRowMapper<Account>(Account.class));
}
@Override
public int queryAccountByCount() throws SQLException {
String sql = "select count(id) from account";
return getJdbcTemplate().queryForObject(sql,int.class);
}
@Override
public List<Account> queryAccountByAsName() throws SQLException {
String sql = "select id ids,name names,money moneys from account";
return getJdbcTemplate().query(sql, new RowMapper<Account>(){
public Account mapRow(ResultSet resultSet,int row)throws SQLException{
Account account = new Account();
account.setId( resultSet.getInt("ids") );
account.setName( resultSet.getString("names"));
account.setMoney( resultSet.getDouble("moneys"));
return account;
}
});
}
}
- applicationContext.xml
<bean id = "accountDao" class="com.itheima.dao.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
Spring的事务控制
Spring的事务机制是Spring给我们提供的一套事务管理的方式,项目中我们就不需要手动去控制事务了
编程式事务:我们的事务控制逻辑(增强逻辑)和业务逻辑混合在一起,比如我们之前的tcf模式控制转账事务,这种方式就叫做编程式事务
声明式事务:通过配置,在不侵犯原有业务逻辑代码的基础上就添加了事务控制功能,这种方式叫做声明式事务(我们这里的Spring声明式事务控制就是通过AOP达到这个目的的)
关于事务
-
事务基本特性(ACID,是针对单个事务的一个完美状态)
- 原子性:一个事务内的操作,要么都成功,要么都失败。很经典的例子:转账,汇款和收款要成功都成功,要失败都失败。
- 一致性:指的是数据的一致性,和原子性其实是一件事情,只不过描述的角度不一样,原子性是从事务的操作的角度,一致性是从数据的角度来描述的,比如转账之前(1000,1000),如果转账100,那么数据状态应该是(900、1100),不应该出现中间状态(900,1000)或者(1000,1100)
- 隔离性:事务并发的时候,比如事务1做的动作给员工涨工资2000块,但是此时事务还没有提交,事务2去查询工资发现工资多了2000块,这就是脏读。解决方法就是建立事务之间的隔离机制。
- 持久性:事务一旦提交,事务提交,变化即生效。即使数据库服务器宕机,那么恢复之后,数据也应该是事务提交之后的状态,不应该回滚到以前了。
-
事务并发问题
-
脏读
财务人员今天心情不好,状态不好,误操作发起事务1给员工张三本月涨了1w块钱工资,但是还没有提交事务
张三发起事务2,查询当月工资,发现多了1W块钱,涨工资了,财务人员发现不对劲,把操作撤回,把涨工资的事务1给回滚了
-
幻读(幻读出现在增加insert和删除delete的时候)
- 比如事务1查询工资表中工资为1w的员工的个数(10个员工),此时事务1还没有结束
- 正在这个时候,事务2,人力部门有两个新员工入职,他们的工资也是1w,人力部门通过事务2向工资表插入了两条记录,并且提交事务了
- 这个时候,事务1又去查询工资为1w的员工个数,发现多了两个员工(12个人),见鬼了,这种情况就叫做幻读
-
不可重复读(出现在修改update的时候)
- 员工发起事务1查询工资,工资为1w,事务1尚未关闭
- 人力部门发起事务2给你涨了工资,涨工资到1.2W(update你的工资表的字段信息),并且提交了事务了。
- 此时,事务1又再次查询自己的工资,发现工资为1.2W,原有的1w这个数据已经读不到了,这就叫做不可重复读
-
事务隔离级别(解决是事务并发问题的)
- 极端模式:读未提交 Read_uncommited,就好比十字路口没有红绿灯一样,效率高,但是风险也高,此时什么事务控制都没有。不要使用这种模式
- 读已提交 Read_commited,顾名思义,其他事务提交之后,才能读取到这个事务提交的数据,这种模式能解决脏读(因为脏读事务没提交造成的)问题,解决不了幻读和不可重复读(因为这两个问题的产生就是insert delete update的时候提交了事务造成)
- 可重复读 Repeatable_Read,可重复读解决脏读和不可重复读
- 极端模式:串行化:所有的事务一个个来,不争不抢,一个事务处理完了,另外一个事务继续进行,这样不会出现并发问题。比如ATM机
- 默认:DEFAULT,默认是数据库的默认,默认模式来源于上面四种模式之一,mysql数据库默认隔离级别可重复读Repeatable_Read,oracle数据库默认级别读已提交Read_commited
- 设置事务隔离级别
- 1 read uncommitted 未提交读,脏读,不可重复读,虚读都可能发生.
- 2 read committed 已提交读,避免脏读,但是不可重复读和虚读有可能发生(Oracle默认)
- 4 repeatable read 可重复读,避免脏读,不可重复读,但是虚读有可能发生(MySql默认)
- 8 serializable 串行化的,避免脏读,不可重复读,虚读的发生
- 查看当前的事务隔离级别:SELECT @@TX_ISOLATION;
- 更改当前的事务隔离级别:SET TRANSACTION ISOLATION LEVEL 四个级别之一
-
事务传播行为
- 我们的事务往往加载service层方法上,那么我们现在的业务简单些,直接service调用dao层方法,以后可能涉及service层方法A()直接调用service层方法B()。那么此时A()和B()都有自己的事务控制,那么相互调用的时候就会有问题啊,A和B应该有一个关于事务的协商机制,这种机制就叫做事务的传播行为
- REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
- SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
- MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
- REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
- NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起 NEVER:以非事务方式运行,如果当前存在事务,抛出异
- NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行REQUIRED类似的操作。
-
Spring的事务管理器PlatformTransactionManager接口
- 实现类:org.springframework.jdbc.datasource.DataSourceTransactionManager 使用Spring JDBC或MyBatis 进行持久化数据时使用
- 实现类:org.springframework.orm.hibernate5.HibernateTransactionManager 使用Hibernate版本进行持久化数据时使用
基于半注解半xml的声明式事务管理
事务管理配置
- 配置spring的事务管理对象
<!-- spring的声明式事务 AOP实现,事务管理类DataSourceTransactionManager-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
- 事务配置
<tx:advice>
通知标签- 属性id:自定义唯一表示
- transaction-manager属性:事务管理类,配置事务管理类的id属性值
- 事务属性配置
<tx:attributes>
子标签<tx:method>
事务方法标签- 属性name:方法名
- 属性read-only:是否只读事务,查询都是只读,其他是非只读
- 属性propagation:事务的传播行为,默认配置REQUIRED或者SUPPORTS
- 属性isolation:事务隔离级别,默认配置DEFAULT
- 属性timeout:事务超时时间,配置-1
- 属性no-rollback-for:遇到什么异常不回滚,配置异常类名,多个类逗号分开
- 属性rollback-for:遇到什么异常回滚
- 以上回滚属性不配置,遇到异常就回滚
- aop切面配置
<aop:config>
标签<aop:advisor>
子标签- 属性advice-ref:引用通知,配置tx:advice标签的属性值
- 属性pointcut:切点配置
- applicationContext.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:aop="http://www.springframework.org/schema/aop"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
">
<context:component-scan base-package="com.itheima"></context:component-scan>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/test"></property>
<property name="username" value="root"></property>
<property name="password" value="root"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
<!-- spring的声明式事务 AOP实现,事务管理类DataSourceTransactionManager-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 事务通知配置-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 事务属性配置-->
<tx:attributes>
<!-- 非只读事务-->
<tx:method name="*" read-only="false" propagation="REQUIRED" isolation="DEFAULT" timeout="-1"></tx:method>
<!-- 查询方法,query开头方法名,配置只读事务-->
<tx:method name="query*" read-only="true" propagation="SUPPORTS"></tx:method>
</tx:attributes>
</tx:advice>
<!-- aop切面配置-->
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.itheima.service.*.*(..))"></aop:advisor>
</aop:config>
</beans>
基于纯注解的声明式事务管理
-
@Transactional注解,取代tx标签
-
@EnableTransactionManagement注解,开启事务注解
-
业务层
@Service("accountService")
@Transactional
public class AccountServiceImpl implements AccountService {
@Autowired
@Qualifier("accountDao")
private AccountDao accountDao;
@Override
public void transfer(String fromName, String toName, double money) throws SQLException {
Account accountFormName = accountDao.queryAccountByName(fromName);
Account accountToName = accountDao.queryAccountByName(toName);
accountFormName.setMoney( accountFormName.getMoney() - money );
accountToName.setMoney( accountToName.getMoney() + money);
accountDao.updateAccount(accountFormName);
int a = 1/0;
accountDao.updateAccount(accountToName);
}
}
- SpringConfig类
@Configuration
@EnableTransactionManagement
@ComponentScan("com.itheima")
@PropertySource("classpath:db.properties")
public class SpringConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean("dataSource")
public DataSource createDataSource(){
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setDriverClassName(driver);
driverManagerDataSource.setUrl(url);
driverManagerDataSource.setUsername(username);
driverManagerDataSource.setPassword(password);
return driverManagerDataSource;
}
@Bean("jdbcTemplate")
public JdbcTemplate createJdbcTemplate(@Qualifier("dataSource") DataSource dataSource){
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
@Bean
public DataSourceTransactionManager createTransactionManager(@Qualifier("dataSource") DataSource dataSource){
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
- 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class MainTest_Anno {
@Autowired
@Qualifier("accountService")
private AccountService service;
@Test
public void testAnnotation() throws SQLException {
service.transfer("张三","李四",100);
}
}