一.使用JdbcTemplate操作数据库
- 创建db.properties配置文件
jdbc.user=common
jdbc.password=common
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.jdbcUrl=jdbc:mysql:///db_spring
jdbc.initPoolSize=5
jdbc.maxPoolSize=10
- 配置Spring配置文件applicationContext.xml
<!-- 使用jdbc配置 -->
<!-- 导入属性文件 classpath代表类路径 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置c3p0数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 使用外部属性文件的属性 -->
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="initialPoolSize" value="${jdbc.initPoolSize}"></property>
<property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
</bean>
<!-- 配置Spring jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 扫描带注解的bean,使用注解方式加载bean -->
<context:component-scan base-package="lxf.spring.jdbc.dao"></context:component-scan>
- 单元测试JDBCTest.java
package lxf.spring.jdbc;
/**
* 测试jdbc操作
* @author lxf
*/
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.sql.DataSource;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import lxf.spring.jdbc.bean.Goods;
import lxf.spring.jdbc.dao.GoodsDao;
public class JDBCTest {
private ApplicationContext ctx= null;
private JdbcTemplate jdbcTemplate;
{
ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");
}
/**
* 获取单个列值,或做统计查询
*/
@Test
public void testQueryTotal()
{
String sql = "SELECT COUNT(goods_id) FROM goods";
long count = jdbcTemplate.queryForObject(sql,long.class);
System.out.println(count);
}
/**
* 查到实体类的集合
*/
@Test
public void testQueryForList()
{
String sql = "SELECT * FROM goods WHERE goods_id > ?";
RowMapper<Goods> rowMapper = new BeanPropertyRowMapper<>(Goods.class);
List<Goods> goods = jdbcTemplate.query(sql, rowMapper,1);
System.out.println(goods);
}
/**
* 从数据库获取一条记录,实际得到对应的对象
*/
@Test
public void testQueryForObject()
{
String sql = "SELECT * FROM goods WHERE goods_id = ?";
RowMapper<Goods> rowMapper = new BeanPropertyRowMapper<>(Goods.class);
Goods goods = jdbcTemplate.queryForObject(sql, rowMapper,1);
System.out.println(goods);
}
/**
* 执行批量更新,批量insert update delete
*/
@Test
public void testBatchUpate()
{
String sql = "INSERT INTO goods (goods_name,cost_price,selling_price,manufactuer) VALUES (?,?,?,?)";
List<Object[]> batchArgs = new ArrayList<Object[]>();
batchArgs.add(new Object[]{"AA","100","101","shanghai"});
batchArgs.add(new Object[]{"BB","200","201","beijing"});
batchArgs.add(new Object[]{"CC","300","301","guangzhou"});
int[] res = jdbcTemplate.batchUpdate(sql,batchArgs);
System.out.println(Arrays.asList(res));
}
/**
* 修改数据
*/
@Test
public void testUpdate()
{
String sql = "UPDATE goods SET goods_name = ? WHERE goods_id = ?";
jdbcTemplate.update(sql,"aaa",1);
}
/**
* 测试获取数据库连接
*/
@Test
public void testDataSource()
{
DataSource dataSource = (DataSource) ctx.getBean("dataSource");
try {
System.out.println(dataSource.getConnection());
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 单元测试dao层查询单条记录
*/
@Test
public void testGetOne()
{
GoodsDao gD = (GoodsDao) ctx.getBean("goodsDao");
Goods goodsOne = gD.getOne(1);
System.out.println(goodsOne);
}
}
- GoodsDao的编写
package lxf.spring.jdbc.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import lxf.spring.jdbc.bean.Goods;
/**
* GoodsDao操作数据库
* @author lxf
*
*/
@Repository
public class GoodsDao {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 根据主键获取goods表单条记录
* @param id
* @return
*/
public Goods getOne(Integer id)
{
String sql = "SELECT * FROM goods WHERE goods_id = ?";
RowMapper<Goods> rowMapper = new BeanPropertyRowMapper<>(Goods.class);
Goods goods=null;
try {
goods = jdbcTemplate.queryForObject(sql, rowMapper,id);
} catch (DataAccessException e) {
e.printStackTrace();
}
return goods;
}
}
二.使用Jdbc具名参数模板NamedParameterJdbcTemplate操作数据库
- 原来的jdbc在sql语句中预加载的是
?
,后续需要对应位置; - 而具名参数就是将预加载的
?
用参数名称
代替
具体使用方式如下:
(1)在Spring配置文件中配置NamedParameterJdbcTemplate的Bean
<!-- 配置NamedParameterJdbcTemplate,该对象可以使用具名参数,其没有无参构造器,只有含有参构造器 -->
<bean id="namedParameterJdbcTemplate"
class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg ref="dataSource"></constructor-arg>
</bean>
(2)使用:
private JdbcTemplate jdbcTemplate;
//具名参数jdbcTemplate
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
{
ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
jdbcTemplate = (JdbcTemplate) ctx.getBean("jdbcTemplate");
namedParameterJdbcTemplate = ctx.getBean(NamedParameterJdbcTemplate.class);
}
/**
* 使用具名参数时,可以使用update(String sql, SqlParameterSource paramSource)方法进行更新操作
* 1.SQL语句中的参数名和类的属性名保持一致
* 2.使用SqlParameterSource接口的实现类BeanPropertySqlParameterSource作为参数
*/
@Test
public void testNameParamJdbc2()
{
String sql = "INSERT INTO goods (goods_name,cost_price,selling_price,manufactuer) " +
"VALUES (:goods_name,:cost_price,:selling_price,:manufactuer)";
Goods goods = new Goods();
goods.setGoods_name("耐克运动鞋");
goods.setCost_price(800);
goods.setSelling_price(699);
goods.setManufactuer("产地山东");
SqlParameterSource paramSource = new BeanPropertySqlParameterSource(goods);
int res = namedParameterJdbcTemplate.update(sql, paramSource);
System.out.println(res);
}
/**
* 测试jdbc具名参数的新增数据库操作
* 好处:若有多个参数则不用在去对应位置,直接对应参数名,便于维护
*/
@Test
public void testNameParamJdbc()
{
String sql = "INSERT INTO goods (goods_name,cost_price,selling_price,manufactuer) VALUES (:gname,:cprice,:sprice,:manf)";
Map<String,Object> paramMap = new HashMap<String,Object>();
paramMap.put("gname", "阿迪达斯运动鞋");
paramMap.put("cprice", "500");
paramMap.put("sprice", "459");
paramMap.put("manf", "产地河北");
int res = namedParameterJdbcTemplate.update(sql, paramMap);
System.out.println(res);
}
三,事务(使用注解的方式配置事务)
Paste_Image.png Paste_Image.png- Spring配置文件配置
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 启用事务注解 ,注意:需要先启用tx命名空间-->
<tx:annotation-driven transaction-manager="transactionManager"/>
- 在需要加事务的业务逻辑方法上加注解:
@Transactional
package lxf.spring.tx;
import javax.management.RuntimeErrorException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class BookShopServiceImpl {
@Autowired
private BookShopDao bookShopDao;
/**
* 用户买书方法
* @param userId 用户账户表account表主键
* @param bookId 书books表主键
*/
//添加事务注解,只在在执行的方法中执行多个业务罗辑之间有一个抛出异常则会执行事务
//如果有异常但是不抛出,则事务不起作用
@Transactional
public void buyBook (int userId, int bookId)
{
//1.获取对应数的单价
double price = bookShopDao.findBookPriceBookId(bookId);
//2.减库存
try {
bookShopDao.updateBookStock(bookId);
} catch (Error e1) {
System.out.println("book stock error");
//e1.printStackTrace();
throw new RuntimeException(e1);
}
//3.扣除用户账户余额
try {
bookShopDao.updateUserAccount(userId, price);
} catch (AccountException e) {
throw new RuntimeException(e);
}
}
}
BookShopDao
package lxf.spring.tx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
/**
* 书店dao
* @author lxf
*/
@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public double findBookPriceBookId(Integer bookId) {
String sql = "SELECT price from books WHERE book_id = ?";
return jdbcTemplate.queryForObject(sql, double.class, bookId);
}
@Override
public void updateBookStock(Integer bookId) {
String sql1 = "SELECT stock from book_stock WHERE book_id= ?";
double stock = jdbcTemplate.queryForObject(sql1, double.class, bookId);
//如果库存不足则抛出异常
if(stock<=0)
{
throw new BookStockException("图书库存不足");
}
String sql = "UPDATE book_stock SET stock = stock-1 WHERE book_id = ?";
jdbcTemplate.update(sql, bookId);
}
@Override
public void updateUserAccount(Integer userId, double price) {
String sql1 = "SELECT balance from acount WHERE id= ?";
int balance = jdbcTemplate.queryForObject(sql1, Integer.class, userId);
//如果用户余额不足则抛出异常
if(balance<=0)
{
throw new AccountException("用户余额不足!");
}
String sql = "UPDATE acount SET balance=balance-? WHERE id = ?";
jdbcTemplate.update(sql, price, userId);
}
}
- 用户余额不足异常定义
package lxf.spring.tx;
/**
* 用户余额不足异常
* @author lxf
*/
public class AccountException extends RuntimeException{
public AccountException() {
super();
// TODO Auto-generated constructor stub
}
public AccountException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
// TODO Auto-generated constructor stub
}
public AccountException(String message, Throwable cause) {
super(message, cause);
// TODO Auto-generated constructor stub
}
public AccountException(String message) {
super(message);
// TODO Auto-generated constructor stub
}
public AccountException(Throwable cause) {
super(cause);
// TODO Auto-generated constructor stub
}
}
四.事务传播行为
Paste_Image.png五.Spring事务传播行为
Paste_Image.png Paste_Image.png六.REQUIED默认的传播行为
- Tom用户的钱只够买一本书,当A用户选择两本书购买的时候,因为第二本书钱不够了,所以
两本书都没有买成功
;
Paste_Image.png
七.REQUIRED_NEW传播行为
- Tom用户的钱只够买一本书,当A用户选择两本书购买的时候,第一本书的事务提交,账户钱成功扣除,库存成功-1; 第二本书因为钱不够事务回滚,账户钱和库存均不变;(
第一本买成功,第二本没买成功
)
Paste_Image.png
八.事务传播行为代码实现
(1)以上代码BookShopServiceImpl修改
@Service
public class BookShopServiceImpl {
@Autowired
private BookShopDao bookShopDao;
/**
* 用户买书方法
* @param userId 用户账户表account表主键
* @param bookId 书books表主键
*/
/**
* 添加事务注解,只在在执行的方法中执行多个业务罗辑之间有一个抛出异常则会执行事务
* 如果有异常但是不抛出,则事务不起作用
* 使用propagation指定事务的传播行为,即当前的事务方法被另外一个事务方法调用时
* 如何使用事务,默认取值为REQUIRED,即使用调用方法的事务
* REQUIRED_NEW:事务自己的事务,调用的事务方法的事务被挂起;
*/
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void buyBook (int userId, int bookId)
{
//1.获取对应数的单价
double price = bookShopDao.findBookPriceBookId(bookId);
//2.减库存
try {
bookShopDao.updateBookStock(bookId);
} catch (Error e1) {
System.out.println("book stock error");
//e1.printStackTrace();
throw new RuntimeException(e1);
}
//3.扣除用户账户余额
try {
bookShopDao.updateUserAccount(userId, price);
} catch (AccountException e) {
throw new RuntimeException(e);
}
}
}
(2)新增收银台接口负责处理用户同事买多本书的桂芬
package lxf.spring.tx;
import java.util.List;
/**
* 收银台接口
* @author lxf
*/
public interface Cashier {
public void checkout(int userId, List<Integer>booksID);
}
(3)Cashier收银台实现类CashierImpl
package lxf.spring.tx;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* 收银台实现类,测试事务的传播行为
* @author lxf
*/
@Service("cashier")
public class CashierImpl implements Cashier {
@Autowired
private BookShopServiceImpl bookShopServiceImpl;
/**
* 一个用户买多本书的情况,新增事务
*/
@Transactional
@Override
public void checkout(int userId, List<Integer> booksID) {
for (Integer bookId : booksID) {
bookShopServiceImpl.buyBook(userId, bookId);
}
}
}
(4)单元测试
ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
cashier = (Cashier)ctx.getBean("cashier");
/**
* 单元测试事务传播行为
*/
@Test
public void testTransChuanbo()
{
int userId= 1;
List<Integer> booksID = Arrays.asList(1,2);
cashier.checkout(userId, booksID);
}
事务属性说明
/**
* 添加事务注解,只在在执行的方法中执行多个业务罗辑之间有一个抛出异常则会执行事务
* 如果有异常但是不抛出,则事务不起作用
* 1.使用propagation指定事务的传播行为,即当前的事务方法被另外一个事务方法调用时
* 如何使用事务,默认取值为REQUIRED,即使用调用方法的事务
* REQUIRED_NEW:事务自己的事务,调用的事务方法的事务被挂起;
* 2.指定事务的隔离级别属性,最常用的取值是:isolation=Isolation.READ_COMMITTED
* 3.默认情况下Spring的声明事务对所有的运行时异常进行回滚,
* 也可以通过对应的属性进行设置,通常情况下取默认值即可,
* 比如:指定发生AccountException异常不回滚,noRollbackFor={AccountException.class}
* 4.使用readOnly指定事务是否是可读,若只读取数据库的方法,
* 应设置readOnly=true,可以帮助数据库引擎优化事务.
* 5.使用timeout属性指定强制回滚之间事务可以占用的时间,单位为:秒
*/
@Transactional(propagation=Propagation.REQUIRES_NEW,
isolation=Isolation.READ_COMMITTED,
readOnly=false,
timeout=5)
九.使用xml配置文件配置事务 applicationContext-xmlconfig-tx.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"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd">
<!-- 使用xml配置文件的方式使用事务-->
<!-- 导入属性文件 classpath代表类路径 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置c3p0数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 使用外部属性文件的属性 -->
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="initialPoolSize" value="${jdbc.initPoolSize}"></property>
<property name="maxPoolSize" value="${jdbc.maxPoolSize}"></property>
</bean>
<!-- 配置Spring jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置bean -->
<bean id="bookShopDao" class="lxf.spring.tx.xmlconfig.BookShopDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<bean id="bookShopService" class="lxf.spring.tx.xmlconfig.service.impl.BookShopServiceImpl">
<property name="bookShopDao" ref="bookShopDao"></property>
</bean>
<bean id="cashier" class="lxf.spring.tx.xmlconfig.service.impl.CashierImpl">
<property name="bookShopServiceImpl" ref="bookShopService"></property>
</bean>
<!--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">
<tx:attributes>
<!--根据方法名指定事务的属性-->
<tx:method name="buyBook" propagation="REQUIRED"/>
<tx:method name="get*" read-only="false"/>
</tx:attributes>
</tx:advice>
<!-- 3.配置切入点,以及把事务切入点和事务属性关联起来 -->
<aop:config>
<!-- 配置切入点 -->
<aop:pointcut expression="execution( * lxf.spring.tx.xmlconfig.service.impl.BookShopServiceImpl.*(..))" id="txPointCut"/>
<!-- 将切入点和属性关联 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
</beans>
十, 手动回滚
- 在需要回滚的地方使用如下代码:
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
- 或先设置回滚点
然后回滚到具体回滚点Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
//回滚到savePoint。 TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);
- 参考文章
网友评论