引子
基于Spring的xml配置实现账户的CRUD案例
这个例子主要是熟悉一下JdbcTemplate的语法
步骤
- 创建java项目,导入坐标
- 编写Account实体类
- 编写AccountDao接口和实现类
- 编写AccountService接口和实现类
- 编写spring核心配置文件
- 编写测试代码
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.15</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.1.5.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.5.RELEASE</version>
</dependency>
</dependencies>
实体以及dao层
public class Account {
private Integer id;
private String name;
private Double money;
}
public interface AccountDao {
public List<Account> findAll();
public Account findById(Integer id);
public void save(Account account);
public void update(Account account);
public void delete(Integer id);
}
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public List<Account> findAll() {
String sql = "select * from account";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<Account>(Account.class));
}
@Override
public Account findById(int id) {
String sql = "select * from account where id = ?";
return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Account>(Account.class), id);
}
@Override
public void save(Account account) {
String sql = "insert into account where values(null, ?, ?)";
jdbcTemplate.update(sql, account.getName(), account.getMoney());
}
@Override
public void update(Account account) {
String sql = "update account set money = ? where name = ?";
jdbcTemplate.update(sql, account.getMoney(), account.getName());
}
@Override
public void delete(int id) {
String sql = "delete from account where id = ?";
jdbcTemplate.update(sql, id);
}
}
服务层
public interface AccountService {
List<Account> findAll();
Account findById(int id);
void save(Account account);
void update(Account account);
void delete(int id);
}
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public List<Account> findAll() {
return accountDao.findAll();
}
@Override
public Account findById(int id) {
return accountDao.findById(id);
}
@Override
public void save(Account account) {
accountDao.save(account);
}
@Override
public void update(Account account) {
accountDao.update(account);
}
@Override
public void delete(int id) {
accountDao.delete(id);
}
}
配置文件
<?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">
<context:component-scan base-package="com.ning"/>
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"/>
</bean>
</beans>
测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:applicationContext.xml"})
public class AccountServiceImplTest {
@Autowired
private AccountService accountService;
@Test
public void testSave() {
Account account = new Account();
account.setName("lux");
account.setMoney(1000d);
accountService.save(account);
}
@Test
public void testFindAll() {
final List<Account> list = accountService.findAll();
for (Account account : list) {
System.out.println(account);
}
}
@Test
public void testFindById() {
final Account byId = accountService.findById(3);
System.out.println(byId);
}
@Test
public void testUpdate() {
Account account = new Account();
account.setName("lux");
account.setMoney(2000d);
accountService.update(account);
}
@Test
public void testDelete() {
accountService.delete(4);
}
}
引子2
实现一个transfer方法
- 创建java项目,导入坐标
- 编写Account实体类
- 编写AccountDao接口和实现类
- 编写AccountService接口和实现类
- 编写spring核心配置文件
- 编写测试代码
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.15</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.1.5.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.5.RELEASE</version>
</dependency>
</dependencies>
entity dao
public class Account {
private Integer id;
private String name;
private Double money;
// setter getter....
}
public interface AccountDao {
public void out(String outUser, Double money);
public void in(String inUser, Double money);
}
@Repository
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void out(String outUser, double money) {
String sql = "update account set money = money - ? where name = ?";
jdbcTemplate.update(sql, money, outUser);
}
@Override
public void in(String inUser, double money) {
String sql = "update account set money = money + ? where name = ?";
jdbcTemplate.update(sql, money, inUser);
}
}
service
public interface AccountService {
void transfer(String outUser, String inUser, double money);
}
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public void transfer(String outUser, String inUser, double money) {
accountDao.out(outUser, money);
accountDao.in(inUser, money);
}
}
核心配置文件
<?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"
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/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">
<context:component-scan base-package="com.ning"/>
<context:property-placeholder location="classpath:jdbc.properties"/>
<aop:aspectj-autoproxy/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"/>
</bean>
</beans>
测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:applicationContext.xml"})
public class AccountServiceImplTest {
@Autowired
private AccountService accountService;
@Test
public void testTransfer() {
accountService.transfer("tom", "jerry", 100d);
}
}
<br />到目前为止其实正常情况下是实现了功能的,唯一的问题在于事务控制。现在的in和out是两个事务。
事务
Spring的事务控制可以分为编程式事务控制和声明式事务控制。
编程式
开发者直接把事务的代码和业务代码耦合到一起,在实际开发中不用。
声明式
开发者采用配置的方式来实现的事务控制,业务代码与事务代码实现解耦合,使用的AOP思想。
编程式事务
PlatformTransactionManager接口,是spring的事务管理器,里面提供了我们常用的操作事务的方法。 <br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/12718322/1635102565290-f86f1e4e-e5d1-43c3-8a7c-33463717013f.png#clientId=ubd96acc2-8a08-4&from=paste&height=134&id=ucf8dbd6f&margin=%5Bobject%20Object%5D&name=image.png&originHeight=267&originWidth=1180&originalType=binary&ratio=1&size=60237&status=done&style=none&taskId=ube6a54ba-3155-42f0-88e2-b6b749fde3c&width=590)
* PlatformTransactionManager 是接口类型,不同的 Dao 层技术则有不同的实现类。
* Dao层技术是jdbcTemplate或mybatis时:
DataSourceTransactionManager
* Dao层技术是hibernate时:
HibernateTransactionManager
* Dao层技术是JPA时:
JpaTransactionManager
TransactionDefinition
TransactionDefinition接口提供事务的定义信息(事务隔离级别、事务传播行为等等)
隔离级别
设置隔离级别,可以解决事务并发产生的问题,如脏读、不可重复读和虚读(幻读)。
* ISOLATION_DEFAULT 使用数据库默认级别
* ISOLATION_READ_UNCOMMITTED 读未提交
* ISOLATION_READ_COMMITTED 读已提交
* ISOLATION_REPEATABLE_READ 可重复读
* ISOLATION_SERIALIZABLE 串行化
事务传播行为
事务传播行为指的就是当一个业务方法【被】另一个业务方法调用时,应该如何进行事务控制。
查询一般设计成SUPPORTS 增删改用REQUIRED
事务状态
TransactionStatus 接口提供的是事务具体的运行状态
可以简单的理解三者的关系:事务管理器通过读取事务定义参数进行事务管理,然后会产生一系列的事 务状态。
声明式事务
声明式事务控制明确事项:
核心业务代码(目标对象) (切入点是谁?)
事务增强代码(Spring已提供事务管理器))(通知是谁?)
切面配置(切面如何配置?)
例子
使用声明式事务完成案例
- 引入tx命名空间
- 事务管理器通知配置
- 事务管理器AOP配置
- 测试事务控制转账业务代码
<?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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/s chema/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.ning"/>
<context:property-placeholder location="classpath:jdbc.properties"/>
<aop:aspectj-autoproxy/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"/>
</bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg name="dataSource" ref="dataSource"/>
</bean>
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false" timeout="-1"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.ning.service.impl.AccountServiceImpl.*(..))"/>
</aop:config>
</beans>
CRUD常用配置
<tx:attributes>
<tx:method name="save*" propagation="REQUIRED"/>
<tx:method name="delete*" propagation="REQUIRED"/>
<tx:method name="update*" propagation="REQUIRED"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
注解方式
在你想加事务的方法前面加这个注解
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.REPEATABLE_READ, timeout = -1, readOnly = false)
注意,注解可以加在类上。
然后修改配置文件,加这个
<tx:annotation-driven/>
纯注解方式
@Configuration
@ComponentScan("com.ning")
@Import(DataSourceConfig.class)
@EnableTransactionManagement
public class SpringConfig {
@Bean
public JdbcTemplate getJdbcTemplate(@Autowired DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
@Bean
public PlatformTransactionManager getPlatformTransactionManager(@Autowired DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
@PropertySource("classpath:jdbc.properties")
public class DataSourceConfig {
@Value("${jdbc.driverClassName}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource getDataSource() {
final DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}
别忘了修改测试类上面的ContextConfiguration注解。
在Servlet中避免多次加载上下文对象
应用上下文对象是通过 new ClasspathXmlApplicationContext(spring配置文件) 方式获取的,但是每次从容器中获得Bean时都要编写 new ClasspathXmlApplicationContext(spring配置文件) , 这样的弊端是配置文件加载多次,应用上下文对象创建多次。
解决思路分析:
在Web项目中,可以使用ServletContextListener监听Web应用的启动,我们可以在Web应用启动时,就加载Spring的配置文件,创建应用上下文对象ApplicationContext,在将其存储到最大的域 servletContext域中,这样就可以在任意位置从域中获得应用上下文ApplicationContext对象了。
Spring提供获取应用上下文的工具
上面的分析不用手动实现,Spring提供了一个监听器ContextLoaderListener就是对上述功能的封 装,该监听器内部加载Spring配置文件,创建应用上下文对象,并存储到ServletContext域中,提供 了一个客户端工具WebApplicationContextUtils供使用者获得应用上下文对象。
所以我们需要做的只有两件事:
- 在web.xml中配置ContextLoaderListener监听器(导入spring-web坐标)
- 使用WebApplicationContextUtils获得应用上下文对象ApplicationContext
<!--全局参数-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--Spring的监听器-->
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
public class AccountServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
final WebApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(req.getServletContext());
final Account account = (Account)context.getBean("account");
System.out.println(account);
}
}