一、传统开发模式问题分析
1、new 关键字将service层的实现类和Dao层的实现类耦合在了一起,当业务逻辑需要切换Dao层实现类的时候必须得修改service代码,不符合面向接口开发的最有原则;
解决方案:
通过 反射 来代替 new 关键字 实例化对象。Class.forName(“全限定类名”),可以把全限定类名配置在xml中。
实现图例:
代码实现:
创建bean.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<bean id="accountDao" class="com.spStudy.dao.impl.JdbcAccountDaoImpl"></bean>
<bean id="transferService" class="com.spStudy.service.impl.TransferServiceImpl">
<property name="AccountDao" ref="accountDao"></property>
</bean>
</beans>
创建BeanFactory
/**
* 工厂类,生产对象(使用反射技术)
*/
public class BeanFactory {
//存储对象
private static Map<String,Object> map = new HashMap<>();
//静态方法,类被jvm虚拟机加载时执行
//读取解析xml,通过反射技术实例化对象并且存储待用
static {
//加载xml
InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream("beans.xml");
//解析xml(dom4j)
SAXReader saxReader = new SAXReader();
try {
Document document = saxReader.read(resourceAsStream);
Element rootElement = document.getRootElement();
List<Element> list = rootElement.selectNodes("//bean");
//实例化bean对象
for (Element element: list) {
String id = element.attributeValue("id");
String clazz = element.attributeValue("class");
//通过反射技术实例化对象
Class<?> aClass = Class.forName(clazz);
Object o = aClass.newInstance();
//将对象存储到map中
map.put(id,o);
}
//维护对象的以来关系
List<Element> properties = rootElement.selectNodes("//property");
for (Element property: properties) {
String name = property.attributeValue("name");
String ref = property.attributeValue("ref");
//找到当前需要被处理的bean
Element parent = property.getParent();
//调用父元素对象的反射功能
String parentId = parent.attributeValue("id");
Object parentObject = map.get(parentId);
//遍历父对象中的所有方法,找到“set”+name
Method[] methods = parentObject.getClass().getMethods();
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
if(method.getName().equalsIgnoreCase("set"+name)){
method.invoke(parentObject,map.get(ref));
}
}
map.put(parentId,parentObject);
}
} catch (DocumentException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
//对外提供获取实例对象的接口(根据id获取)
public static Object getBean(String id){
return map.get(id);
}
}
通过BeanFactory获取实例化的对象
private TransferService transferService = (TransferService) BeanFactory.getBean("transferService");
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao){
this.accountDao = accountDao;
}
2、service层没有添加业务控制,出现异常时可能导致数据错乱;
数据控制主要时在Connection的事务:
- connection.commit(),事务的提交
- connection,rollback(),事务的回滚
@Override
public void transfer(String fromCardNo, String toCardNo, int money) throws Exception {
Account from = accountDao.queryAccountByCardNo(fromCardNo);
Account to = accountDao.queryAccountByCardNo(toCardNo);
from.setMoney(from.getMoney()-money);
to.setMoney(to.getMoney()+money);
//两次修改使用两个connection链接
accountDao.updateAccountByCardNo(to);
accountDao.updateAccountByCardNo(from);
}
事务控制目前是放在了Dao层,没有在service层
@Override
public int updateAccountByCardNo(Account account) throws Exception {
Connection con = DruidUtils.getInstance().getConnection();
String sql = "update account set money = ? where card_no = ?";
PreparedStatement preparedStatement = con.prepareStatement(sql);
preparedStatement.setInt(1,account.getMoney());
preparedStatement.setString(2,account.getCardNo());
int i = preparedStatement.executeUpdate();
preparedStatement.close();
return i;
}
解决思路
1.让两次update使用同一个connection连接,两次update属于同一个线程内的执行调用,我们可以给当前线程绑定一个Connection,和当前线程有关系的数据库操作都去使用这个Connection(从当前线程中去拿)。
2.把事务控制添加在service层
代码实现
创建ConnectionUtils,控制同一线程中是同一个Connection
public class ConnectionUtils {
private static ConnectionUtils connectionUtils = new ConnectionUtils();
private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
private ConnectionUtils(){
}
public static ConnectionUtils getInstance(){
return connectionUtils;
}
public Connection getCurrentConnection() throws SQLException {
//判断当前线程是都已经绑定链接,如果没有绑定,从连接池获取一个链接绑定到当前线程
Connection connection = threadLocal.get();
if(connection == null){
connection = DruidUtils.getInstance().getConnection();
threadLocal.set(connection);
}
return connection;
}
}
创建TransactionManager,事务管理器,负责手动事务的开启,提交,回滚。
/**
* 事务管理器,负责手动事务的开启,提交,回滚
*/
public class TransactionManager {
private TransactionManager(){
}
private static TransactionManager transactionManager = new TransactionManager();
public static TransactionManager getInstance(){
return transactionManager;
}
//开启手动事务控制
public void beginTransaction() throws SQLException {
ConnectionUtils.getInstance().getCurrentConnection().setAutoCommit(false);
}
//提交事务
public void commit() throws SQLException {
ConnectionUtils.getInstance().getCurrentConnection().commit();
}
//回滚事务
public void rollback() throws SQLException {
ConnectionUtils.getInstance().getCurrentConnection().rollback();
}
}
在service层添加事务控制代码逻辑
@Override
public void transfer(String fromCardNo, String toCardNo, int money) throws Exception {
try {
//开启事务(关闭事务的自动提交)
TransactionManager.getInstance().beginTransaction();
Account from = accountDao.queryAccountByCardNo(fromCardNo);
Account to = accountDao.queryAccountByCardNo(toCardNo);
from.setMoney(from.getMoney()-money);
to.setMoney(to.getMoney()+money);
//两次修改使用两个connection链接
accountDao.updateAccountByCardNo(to);
int i = 1/0;//设置错误代码
accountDao.updateAccountByCardNo(from);
//提交事务
TransactionManager.getInstance().commit();
}catch (Exception e){
e.printStackTrace();
//回滚事务
TransactionManager.getInstance().rollback();
//抛出异常
throw e;
}
}
3、事务控制代码与业务代码耦合,不易扩展
解决思路:
事务控制代码属于代码逻辑中的横切逻辑,使用动态代理模式进行优化,将事务控制代码与业务代码进行分离。
在实现代理模式前梳理类的依赖关系,整合代码
ConnectionUtils:确保一个线程使用一个链接,被JdbcAccountDaoImpl和TransactionManager使用;
TransactionManager:事务管理器,负责手动事务的开启,提交,回滚,将被代理工厂 类使用加强事务控制逻辑;
ProxyFacatory:代理工厂类,用来动态生成代理对象,使用TransactionManager类
根据类的依赖关系整合bean.xml
<?xml version="1.0" encoding="UTF-8" ?>
<beans>
<bean id="accountDao" class="com.spStudy.dao.impl.JdbcAccountDaoImpl">
<property name="ConnectionUtils" ref="connectionUtils"></property>
</bean>
<bean id="transferService" class="com.spStudy.service.impl.TransferServiceImpl">
<property name="AccountDao" ref="accountDao"></property>
</bean>
<!-- 配置新增的三个组件 -->
<!-- 获取数据库链接 -->
<bean id="connectionUtils" class="com.spStudy.utils.ConnectionUtils"></bean>
<!-- 事务管理器 -->
<bean id="transactionManager" class="com.spStudy.utils.TransactionManager">
<property name="ConnectionUtils" ref="connectionUtils"></property>
</bean>
<!-- 代理对象工厂 -->
<bean id="proxyFacatory" class="com.spStudy.factory.ProxyFacatory">
<property name="TransactionManager" ref="transactionManager" ></property>
</bean>
</beans>
优化ConnectionUtils、TransactionManager和service
public class ConnectionUtils {
private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
public Connection getCurrentConnection() throws SQLException {
//判断当前线程是都已经绑定链接,如果没有绑定,从连接池获取一个链接绑定到当前线程
Connection connection = threadLocal.get();
if(connection == null){
connection = DruidUtils.getInstance().getConnection();
threadLocal.set(connection);
}
return connection;
}
}
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
//开启手动事务控制
public void beginTransaction() throws SQLException {
connectionUtils.getCurrentConnection().setAutoCommit(false);
}
//提交事务
public void commit() throws SQLException {
connectionUtils.getCurrentConnection().commit();
}
//回滚事务
public void rollback() throws SQLException {
connectionUtils.getCurrentConnection().rollback();
}
}
public class TransferServiceImpl implements TransferService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao){
this.accountDao = accountDao;
}
@Override
public void transfer(String fromCardNo, String toCardNo, int money) throws Exception {
Account from = accountDao.queryAccountByCardNo(fromCardNo);
Account to = accountDao.queryAccountByCardNo(toCardNo);
from.setMoney(from.getMoney()-money);
to.setMoney(to.getMoney()+money);
//两次修改使用两个connection链接
accountDao.updateAccountByCardNo(to);
int i = 1/0;
accountDao.updateAccountByCardNo(from);
}
}
创建代理工厂
public class ProxyFacatory {
private TransactionManager transactionManager;
public void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public Object getJDKProxy(Object object){
return Proxy.newProxyInstance(object.getClass().getClassLoader(), object.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//增强逻辑
try {
Object o = null;
transactionManager.beginTransaction();
o = method.invoke(object, args);
transactionManager.commit();
return o;
}catch (Exception e){
e.printStackTrace();
transactionManager.rollback();
throw e;
}
}
});
}
public Object getCglibProxy(Object object){
return Enhancer.create(object.getClass(), new MethodInterceptor() {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
try {
Object obj = null;
transactionManager.beginTransaction();
o = method.invoke(object,objects);
transactionManager.commit();
return o;
}catch (Exception e){
e.printStackTrace();
transactionManager.rollback();
throw e;
}
}
});
}
}
servlet调用
//从BeanFactory获取到proxyFactory代理工厂的实例化对象
private ProxyFacatory proxyFacatory = (ProxyFacatory)BeanFactory.getBean("proxyFacatory");
private TransferService transferService = (TransferService) proxyFacatory.getJDKProxy(BeanFactory.getBean("transferService"));
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//设置请求体的字符编码
req.setCharacterEncoding("UTF-8");
//接收参数
String fromCardNo = req.getParameter("fromCardNo");
String toCardNo = req.getParameter("toCardNo");
String moneyStr = req.getParameter("money");
int money = Integer.parseInt(moneyStr);
Result result = new Result();
try {
transferService.transfer(fromCardNo,toCardNo,money);
result.setStatus("200");
result.setMessage("转账成功!!");
} catch (Exception e) {
e.printStackTrace();
result.setStatus("201");
result.setMessage(e.toString());
}
// 响应
resp.setContentType("application/json;charset=utf-8");
resp.getWriter().print(JsonUtils.object2Json(result));
}