基本的项目目录:
首先要加入的pom.xml配置,也就是引入的依赖,代码如下:
<!-- spring 测试环境 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.3.2.RELEASE</version> <scope>test</scope> </dependency> <!-- spring 框架坐标依赖添加 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.2.RELEASE</version> </dependency> <!-- aop --> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency> <!-- mysql 驱动包 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.39</version> </dependency> <!-- c3p0 连接池 --> <dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <!-- spring jdbc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>4.3.2.RELEASE</version> </dependency> <!-- spring事物 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>4.3.2.RELEASE</version> </dependency>
然后准备个properties文件,用来准备mysql的配置,
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/spring_jdbc?useUnicode=true&characterEncoding=utf8 jdbc.user=root jdbc.password=root
然后准备jdbc的配置文件:spring-jdbc.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: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/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.shsxt"></context:component-scan> <!-- 加载properties 配置文件 --> <context:property-placeholder location="classpath:jdbc.properties"/> <aop:aspectj-autoproxy/> <!-- 配置数据源c3p0 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"></property> <property name="jdbcUrl" value="${jdbc.url}"></property> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!-- 配置 jdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 事物配置 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 事物通知配置 --> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <!-- 所有以save 开头的方法 均会引入事物控制 --> <tx:method name="save*" propagation="REQUIRED"/> <tx:method name="update*" propagation="REQUIRED"/> <tx:method name="del*" propagation="REQUIRED"></tx:method> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="cut" expression="execution(* com.shsxt.service..*.*(..))"></aop:pointcut> <aop:advisor advice-ref="txAdvice" pointcut-ref="cut"></aop:advisor> </aop:config> </beans>
首先定义个接口:AccountDao
package com.shsxt.dao; import com.shsxt.po.Account; import java.math.BigDecimal; import java.util.List; public interface AccountDao { /** * 添加记录返回影响行数 * @param account * @return */ public Integer saveAccount(Account account); /** * 添加记录返回主键 * @param account * @return */ public Integer saveAccountHasPrimaryKey(Account account); /** * 批量添加账户记录 返回影响行数 * @param accounts * @return */ public Integer saveAccountsBatch(List<Account> accounts); /** * 统计账户记录-聚合查询 * sum max min count * group by having * @param userId * @return */ public Integer countAccountsByUserId(Integer userId); /** * 根据id 查询记录详情 * @param userId * @return */ public Account queryAccountById(Integer userId); /** * 多条件查询 * @param userId * @param type * @param createTime * @param aname * @return */ public List<Account> queryAccountsByParams(Integer userId,String type,String createTime,String aname); /** * 更新账户记录 * @param account * @return */ public Integer updateAccount(Account account); /** * 批量更新 * @param accounts * @return */ public Integer updateAccountsBatch(List<Account> accounts); /** * 根据id 删除记录 * @param id * @return */ public Integer deleteAccountById(Integer id); /** * 批量删除 * @param ids * @return */ public Integer deleteAccountsBatch(Integer[] ids); /** * 账户支出方法 * @param sourceAid 原账户 * @param money 支出金额 * @return */ public Integer outMoney(Integer sourceAid, BigDecimal money); /** * 账户收入方法 * @param targetAid * @param monet * @return */ public Integer inMoney(Integer targetAid,BigDecimal monet); }
数据库表:
对应的实体类Account:
package com.shsxt.po; import java.math.BigDecimal; import java.util.Date; public class Account { private Integer id; private String name; private BigDecimal money; private Integer userId; private Date createTime; private Date updateTime; private String remark; private String type; public Account() { } public Account(String name, BigDecimal money, Integer userId, Date createTime, Date updateTime, String remark, String type) { this.name = name; this.money = money; this.userId = userId; this.createTime = createTime; this.updateTime = updateTime; this.remark = remark; this.type = type; } @Override public String toString() { return "Account{" + "id=" + id + ", name='" + name + '\'' + ", money=" + money + ", userId=" + userId + ", createTime=" + createTime + ", updateTime=" + updateTime + ", remark='" + remark + '\'' + ", type='" + type + '\'' + '}'; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public BigDecimal getMoney() { return money; } public void setMoney(BigDecimal money) { this.money = money; } public Integer getUserId() { return userId; } public void setUserId(Integer userId) { this.userId = userId; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } public Date getUpdateTime() { return updateTime; } public void setUpdateTime(Date updateTime) { this.updateTime = updateTime; } public String getRemark() { return remark; } public void setRemark(String remark) { this.remark = remark; } public String getType() { return type; } public void setType(String type) { this.type = type; } }
对应的实现类:AccountDaoImpl如下
package com.shsxt.dao.impl; import com.shsxt.dao.AccountDao; import com.shsxt.po.Account; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.core.PreparedStatementCreator; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; import javax.annotation.Resource; import java.math.BigDecimal; import java.sql.*; import java.util.ArrayList; import java.util.Date; import java.util.List; @Repository public class AccountDaoImpl implements AccountDao { @Resource private JdbcTemplate jdbcTemplate; @Override public Integer saveAccount(Account account) { String sql="insert into account(aname,type,money,user_id,create_time,update_time,remark) " + " values(?,?,?,?,?,?,?)"; return jdbcTemplate.update(sql,account.getName(),account.getType(),account.getMoney(),account.getUserId(),account.getCreateTime(),account.getUpdateTime(),account.getRemark()); } @Override public Integer saveAccountHasPrimaryKey(Account account) { String sql="insert into account(aname,type,money,user_id,create_time,update_time,remark) " + " values(?,?,?,?,?,?,?)"; KeyHolder keyHolder=new GeneratedKeyHolder(); jdbcTemplate.update(new PreparedStatementCreator() { @Override public PreparedStatement createPreparedStatement(Connection con) throws SQLException { PreparedStatement ps= con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); ps.setString(1,account.getName()); ps.setString(2,account.getType()); ps.setBigDecimal(3,account.getMoney()); ps.setInt(4,account.getUserId()); ps.setObject(5,account.getCreateTime()); ps.setObject(6,account.getUpdateTime()); ps.setString(7,account.getRemark()); return ps; } }, keyHolder); // 返回主键 return keyHolder.getKey().intValue(); } @Override public Integer saveAccountsBatch(List<Account> accounts) { String sql="insert into account(aname,type,money,user_id,create_time,update_time,remark) " + " values(?,?,?,?,?,?,?)"; return jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { Account account= accounts.get(i); ps.setString(1,account.getName()); ps.setString(2,account.getType()); ps.setBigDecimal(3,account.getMoney()); ps.setInt(4,account.getUserId()); ps.setObject(5,account.getCreateTime()); ps.setObject(6,account.getUpdateTime()); ps.setString(7,account.getRemark()); } @Override public int getBatchSize() { // 批量添加记录的总条数 return accounts.size(); } }).length; } @Override public Integer countAccountsByUserId(Integer userId) { String sql="select count(1) from account where user_id=? "; return jdbcTemplate.queryForObject(sql,Integer.class,userId); } @Override public Account queryAccountById(Integer id) { String sql="select id,aname,type,money,user_id,create_time,update_time,remark from account where id=?"; return (Account) jdbcTemplate.queryForObject(sql, new Object[]{id}, new RowMapper<Account>() { @Override public Account mapRow(ResultSet rs, int rowNum) throws SQLException { Account account=new Account(); account.setType(rs.getString("type")); account.setUpdateTime(rs.getDate("update_time")); account.setCreateTime(rs.getDate("create_time")); account.setRemark(rs.getString("remark")); account.setUserId(rs.getInt("user_id")); account.setId(rs.getInt("id")); account.setMoney(rs.getBigDecimal("money")); account.setName(rs.getString("aname")); return account; } }); } @Override public List<Account> queryAccountsByParams(Integer userId, String type, String createTime, String aname) { List<Object> params=new ArrayList<Object>(); StringBuffer sql=new StringBuffer("select id,aname,type,money,user_id,create_time,update_time,remark from account where 1=1 "); if(null !=userId){ sql.append(" and user_id=? "); params.add(userId); } if(null !=type && !("".equals(type.trim()))){ sql.append(" and type=? "); params.add(type); } if(null !=createTime && !("".equals(createTime.trim()))){ sql.append(" and create_time < ? "); params.add(createTime); } if(null !=aname && !("".equals(aname.trim()))){ sql.append(" and aname like concat('%',?,'%') "); params.add(aname); } return jdbcTemplate.query(sql.toString(), params.toArray(), new RowMapper<Account>() { @Override public Account mapRow(ResultSet rs, int rowNum) throws SQLException { Account account=new Account(); account.setType(rs.getString("type")); account.setUpdateTime(rs.getDate("update_time")); account.setCreateTime(rs.getDate("create_time")); account.setRemark(rs.getString("remark")); account.setUserId(rs.getInt("user_id")); account.setId(rs.getInt("id")); account.setMoney(rs.getBigDecimal("money")); account.setName(rs.getString("aname")); return account; } }); } @Override public Integer updateAccount(Account account) { String sql="update account set aname=?,type=?,money=? ,update_time=? where id=?"; return jdbcTemplate.update(sql,account.getName(),account.getType(), account.getMoney(),account.getUpdateTime(),account.getId()); } @Override public Integer updateAccountsBatch(List<Account> accounts) { String sql="update account set aname=?,type=?,money=? ,update_time=? where id=?"; return jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { Account account=accounts.get(i); ps.setString(1,account.getName()); ps.setString(2,account.getType()); ps.setBigDecimal(3,account.getMoney()); ps.setObject(4,account.getUpdateTime()); ps.setInt(5,account.getId()); } @Override public int getBatchSize() { return accounts.size(); } }).length; } @Override public Integer deleteAccountById(Integer id) { String sql="delete from account where id=?"; return jdbcTemplate.update(sql,id); } @Override public Integer deleteAccountsBatch(Integer[] ids) { String sql="delete from account where id=?"; return jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() { @Override public void setValues(PreparedStatement ps, int i) throws SQLException { ps.setInt(1,ids[i]); } @Override public int getBatchSize() { return ids.length; } }).length; } @Override public Integer outMoney(Integer sourceAid, BigDecimal money) { String sql="update account set money=money-? where id=? "; return jdbcTemplate.update(sql,money,sourceAid); } @Override public Integer inMoney(Integer targetAid, BigDecimal money) { String sql="update account set money=money+? where id=? "; return jdbcTemplate.update(sql,money,targetAid); } }
对应的测试类Test如下:
package com.shsxt; import com.shsxt.dao.AccountDao; import com.shsxt.po.Account; import org.junit.Test; import javax.annotation.Resource; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Date; import java.util.List; public class TestAccountDao extends TestBase { @Resource private AccountDao accountDao; @Test public void test01(){ System.out.println(accountDao.saveAccount(new Account("我的第一桶金", BigDecimal.valueOf(8000),1,new Date(),new Date(),"我的第一桶金","1"))); } @Test public void test02(){ System.out.println(accountDao.saveAccountHasPrimaryKey(new Account("我的第一桶金", BigDecimal.valueOf(8000),1,new Date(),new Date(),"我的第一桶金","1"))); } @Test public void test03(){ List<Account> accounts=new ArrayList<Account>(); for(int i=80;i<90;i++){ accounts.add(new Account("我的第一桶金"+i, BigDecimal.valueOf(8000),1,new Date(),new Date(),"我的第一桶金"+i,"1")); } System.out.println(accountDao.saveAccountsBatch(accounts)); } @Test public void test04(){ System.out.println(accountDao.countAccountsByUserId(1)); } @Test public void test05(){ System.out.println(accountDao.queryAccountById(82)); } @Test public void test06(){ Account account=accountDao.queryAccountById(82); account.setType("2"); account.setMoney(BigDecimal.valueOf(10000)); System.out.println(accountDao.updateAccount(account)); } @Test public void test07(){ List<Account> accounts=accountDao.queryAccountsByParams(1,"2","2018-07-05","test"); for(Account account:accounts){ account.setType("3"); account.setMoney(BigDecimal.valueOf(10000)); account.setUpdateTime(new Date()); } System.out.println(accountDao.updateAccountsBatch(accounts)); } @Test public void test08(){ System.out.println(accountDao.deleteAccountById(82)); } @Test public void test09(){ System.out.println(accountDao.deleteAccountsBatch(new Integer[]{80,81})); } }
原子性(Atomicity) : 共生死,要么全部成功,要么全部失败! 一致性(Consistency) : 事务在执行前后,数据库中数据要保持一致性状态。(如转账的过程 账户操作后数据必须保持一致) 隔离性(Isolation) : 事务与事务之间的执行应当是相互隔离互不影响的。(多个角色对统一记录进行操作必须保证没有任何干扰),当然没有影响 是不可能的,为了让影响级别降到最低,通过隔离级别加以限制: 1. READ_UNCOMMITTED 2. READ_COMMITTED 3. REPEATABLE_READ 4. SERIALIZABLE 持久性(Durability) :事务提交完毕后,数据库中的数据的改变是永久的。
Spring 事务管理的实现有许多细节,如果对整个接口框架有个大体了解会非常有利于我们理解事务,下面通过讲解 Spring 的事务接口来了解 Spring 实现事务的具体策略。
Spring 并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给 Hibernate 或者 JTA 等持久化机制所提供的相关平台框架 的事务来实现。Spring 事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口, Spring 为各 个平台如 JDBC、 Hibernate 等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
事务的配置文件spring-jdbc.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: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/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.shsxt"></context:component-scan> <!-- 加载properties 配置文件 --> <context:property-placeholder location="classpath:jdbc.properties"/> <aop:aspectj-autoproxy/> <!-- 配置数据源c3p0 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"></property> <property name="jdbcUrl" value="${jdbc.url}"></property> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!-- 配置 jdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 事物配置 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 事物通知配置 --> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <!-- 所有以save 开头的方法 均会引入事物控制 --> <tx:method name="save*" propagation="REQUIRED"/> <tx:method name="add*" propagation="REQUIRED"/> <tx:method name="insert*" propagation="REQUIRED"/> <tx:method name="update*" propagation="REQUIRED"/> <tx:method name="up*" propagation="REQUIRED"/> <tx:method name="del*" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="cut" expression="execution(* com.shsxt.service..*.*(..))"></aop:pointcut> <aop:advisor advice-ref="txAdvice" pointcut-ref="cut"></aop:advisor> </aop:config> <tx:annotation-driven transaction-manager="txManager"/> </beans>
接口如下:
package com.shsxt.service; import java.math.BigDecimal; public interface AccountService { /** * 转账方法定义 * @param sourceAid * @param targetAid * @param money */ public Integer updateAccountInfo(Integer sourceAid, Integer targetAid, BigDecimal money); }
实现类如下:
package com.shsxt.service.impl; import com.shsxt.dao.AccountDao; import com.shsxt.service.AccountService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.math.BigDecimal; @Service public class AccountServiceImpl02 implements AccountService { @Resource private AccountDao accountDao; @Override @Transactional(propagation = Propagation.REQUIRED) public Integer updateAccountInfo(Integer sourceAid, Integer targetAid, BigDecimal money) { System.out.println("AccountServiceImpl02.updateAccountInfo..."); Integer result = 0; Integer r1 = accountDao.outMoney(sourceAid, money); int a = 1 / 0;// 运行期异常 spring 事物捕捉到该类型异常后进行回滚操作 Integer r2 = accountDao.inMoney(targetAid, money); if (r1 > 0 && r2 > 0) { result = 1; } return result; } }
测试类:
package com.shsxt; import com.shsxt.service.AccountService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import javax.annotation.Resource; import java.math.BigDecimal; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:spring-jdbc_02.xml"}) public class TestAccountService02 { @Resource(name = "accountServiceImpl02") private AccountService accountService; @Test public void test(){ Integer result= accountService.updateAccountInfo(65,66, BigDecimal.valueOf(100)); System.out.println(result); } }
上面的程序不会因为转账的时候出现异常而出现金额的消失,而是会对事物回滚.
与上面的基于xml配置不同的就是配置首先来看看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: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/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.shsxt"></context:component-scan> <!-- 加载properties 配置文件 --> <context:property-placeholder location="classpath:jdbc.properties"/> <aop:aspectj-autoproxy/> <!-- 配置数据源c3p0 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driver}"></property> <property name="jdbcUrl" value="${jdbc.url}"></property> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!-- 配置 jdbcTemplate --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 事物配置 --> <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 事物通知配置 --> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <!-- 所有以save 开头的方法 均会引入事物控制 --> <tx:method name="save*" propagation="REQUIRED"/> <tx:method name="add*" propagation="REQUIRED"/> <tx:method name="insert*" propagation="REQUIRED"/> <tx:method name="update*" propagation="REQUIRED"/> <tx:method name="up*" propagation="REQUIRED"/> <tx:method name="del*" propagation="REQUIRED"/> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="cut" expression="execution(* com.shsxt.service..*.*(..))"></aop:pointcut> <aop:advisor advice-ref="txAdvice" pointcut-ref="cut"></aop:advisor> </aop:config> <tx:annotation-driven transaction-manager="txManager"/> </beans>
接口代码:
package com.shsxt.service; import java.math.BigDecimal; public interface AccountService03 { /** * 转账方法定义 * @param sourceAid * @param targetAid * @param money */ public Integer abcAccountInfo(Integer sourceAid, Integer targetAid, BigDecimal money); }
实现类的实现:
package com.shsxt.service.impl; import com.shsxt.dao.AccountDao; import com.shsxt.service.AccountService; import com.shsxt.service.AccountService03; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; import java.math.BigDecimal; @Service public class AccountServiceImpl03 implements AccountService03 { @Resource private AccountDao accountDao; @Override @Transactional(propagation = Propagation.REQUIRED) public Integer abcAccountInfo(Integer sourceAid, Integer targetAid, BigDecimal money) { System.out.println("AccountServiceImpl03.updateAccountInfo..."); Integer result = 0; Integer r1 = accountDao.outMoney(sourceAid, money); int a = 1 / 0;// 运行期异常 spring 事物捕捉到该类型异常后进行回滚操作 Integer r2 = accountDao.inMoney(targetAid, money); if (r1 > 0 && r2 > 0) { result = 1; } return result; } }
Java RMI 指的是远程方法调用 (Remote Method Invocation)。它是一种机制,能够让在某个 Java 虚拟机上的对象调用另一个 Java 虚拟机中的对象上的方法。 JVM 可以位于相同或不同计算机上,在多个 JVM 中,一个 JVM 可以调用存储在其它 JVM 的对象的方法。RMI 是 Java 编程语言里,一种用于实现远程过程调用的应用程序编程接口,它是客户机上运行的程序可以调用远程服务器上的对象,远程方法调用特性使 Java 编程人员能够在网络环境中分布操作。 RMI 全部的宗旨就是尽可能简化远程接口对象的使用。
Java RMI 极大地依赖于接口,在需要创建一个远程对象的时候,程序员通过传递一个接口来隐藏底层的实现细节,客户端得到的远程对象句柄正好与本地的根代码连接,由后者负责透过网络通信。这样一来,程序员只需关心如何通过自己的接口句柄发送消息。Spring 的优势除了 IOC 和 AOP 外,还提供了简易的服务抽象。如远程 rmi 调用。 远程方法调用,即 Java RMI(java Remote Method Invocation)。
RMI 底层封装了 Socket 反射机制。java 语言当中 分布式的基础!!!实现 java 之间的互相访问。 RMI 本质就是使用代理类来封装 Socket 的通信细节!! RMI 的 API 帮组我们创建一个代理类及其内容!!使用这个代理类实例就可以远程通信。
1).Remote 接口:每一个要暴露出去的 java 类,都需要实现 Remote 接口,并且所有的方法必须抛出 RemoteException
2).UnicastRemoteObject 类:服务端程序的实现方案之一就是继承这个类,无参构造器也要抛出 RemoteException
3).LocateRegistry 类创建能在特定接口接受调用远程对象注册服务程序。
public static Registry createRegistry(int port) throws RemoteException
4).Naming 类提供了存储和获得远程对象注册服务程序中的远程对象进行引用的方法
public static Remote lookup(String name) throws NotBoundException,MalformedURException,ReoteException public static void bind(String name,Remote obj) throws AlreadyBoundException,MalforedURException,RemoteException
项目结构:maven
首先在pom.xml中配置的依赖为:
<!-- 加入api 坐标依赖 --> <dependency> <groupId>com.shsxt</groupId> <artifactId>rmi_api</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
rmi_api的项目结构为:
User类如下:
package com.shsxt.po; import java.io.Serializable; public class User implements Serializable{ private static final long serialVersionUID = 8591223884736200805L; private Integer id; private String userName; private String userPwd; public User() { } public User(Integer id, String userName, String userPwd) { this.id = id; this.userName = userName; this.userPwd = userPwd; } @Override public String toString() { return "User{" + "id=" + id + ", userName='" + userName + '\'' + ", userPwd='" + userPwd + '\'' + '}'; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getUserPwd() { return userPwd; } public void setUserPwd(String userPwd) { this.userPwd = userPwd; } }
接口:
package com.shsxt.service; import com.shsxt.po.User; import java.rmi.Remote; import java.rmi.RemoteException; /** * 定义对外提供服务的接口 */ public interface IUserService extends Remote{ public User queryUserByUserId(Integer userId) throws RemoteException; }
rmi.client项目结构:
UserController类
package com.shsxt.controller; import com.shsxt.po.User; import com.shsxt.service.IUserService; import java.rmi.RemoteException; public class UserController { private IUserService userService; public void setUserService(IUserService userService) { this.userService = userService; } public User queryUserByUserId(Integer userId) throws RemoteException{ return userService.queryUserByUserId(userId); } }
测试Test类:
package com.shsxt; import com.shsxt.controller.UserController; import com.shsxt.po.User; import com.shsxt.service.IUserService; import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.NotBoundException; import java.rmi.RemoteException; public class Test { public static void main(String[] args) throws RemoteException, NotBoundException, MalformedURLException { UserController userController=new UserController(); // 查找远程服务 获取本地代理对象 IUserService userService= (IUserService) Naming.lookup("rmi://127.0.0.1:8989/userService"); userController.setUserService(userService); User user= userController.queryUserByUserId(2); System.out.println(user); } }
rmi.server的项目结构
UserService类:
package com.shsxt.service; import com.shsxt.po.User; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; import java.util.HashMap; import java.util.Map; /** * 服务实现 */ public class UserService extends UnicastRemoteObject implements IUserService { private Map<Integer,User> map=new HashMap<Integer,User>(); public UserService() throws RemoteException { map.put(1,new User(1,"admin","123456")); map.put(2,new User(2,"shsxt","123456")); } @Override public User queryUserByUserId(Integer userId) throws RemoteException { System.out.println("服务端收到查询请求,userId-->"+userId); return map.get(userId); } }
Publish类:
package com.shsxt; import com.shsxt.service.UserService; import java.net.MalformedURLException; import java.rmi.AlreadyBoundException; import java.rmi.Naming; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; public class Publish { public static void main(String[] args) throws RemoteException, AlreadyBoundException, MalformedURLException { //注册端口号 LocateRegistry.createRegistry(8989); // 对外发布服务 Naming.bind("rmi://127.0.0.1:8989/userService",new UserService()); System.out.println("服务发布成功!!!"); } }
先安装主项目(install),再运行服务器,最后运行客户端就行啦:
使用 spring 的 RMI,提供的服务简单方便,不用继承接口 和指定的类,不用抛出异常。
服务端接口声明:
package com.shsxt.service; public interface IHelloService { public String sayHello(String msg); }
1.接口实现
package com.shsxt.service.impl; import org.springframework.stereotype.Service; import com.shsxt.service.IHelloService; @Service public class HelloServiceImpl implements IHelloService { @Override public String sayHello(String msg) { System.out.println("服务器端收到信息: "+msg); return "hello:"+msg; } }
2.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 http://www.springframework.org/schema/context/springcontext.xsd"> <!-- 定义扫描范围 --> <context:component-scan basepackage="com.shsxt"></context:component-scan> <bean class="org.springframework.remoting.rmi.RmiServiceExporter"> <property name="serviceName" value="hello"></property> <property name="service" ref="helloServiceImpl"></property> <property name="serviceInterface" value="com.shsxt.service.IHelloService"></property> <property name="registryPort" value="1199"></property> </bean> </beans
3.对外发布远程服务
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class Publist { public static void main(String[] args) { /** * 启动 spring ioc 容器 ,并发布对应服务 */ ApplicationContext ac=new ClassPathXmlApplicationContext("beans.xml"); } }
客户端调用
1.接口声明
package com.shsxt.service; public interface IHelloService { public String sayHello(String msg); }
2.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 http://www.springframework.org/schema/context/springcontext.xsd"> <!-- 定义扫描范围 --> <context:component-scan basepackage="com.shsxt"></context:component-scan> <bean class="org.springframework.remoting.rmi.RmiProxyFactoryBean"> <property name="serviceUrl" value="rmi://localhost:1199/hello"></property> <property name="serviceInterface" value="com.shsxt.service.IHelloService"></property> </bean> </beans>
3.客户端使用远程接口方法服务
package com.shsxt.service.impl; import javax.annotation.Resource; import org.springframework.stereotype.Service; import com.shsxt.service.IHelloService; @Service public class UserServiceImpl { @Resource private IHelloService helloService; public void test(){ System.out.println(helloService.sayHello("im win10")); } }
4.客户端测试
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.shsxt.service.IHelloService; import com.shsxt.service.impl.UserServiceImpl; public class Test { public static void main(String[] args) { ApplicationContext ac=new ClassPathXmlApplicationContext("beans.xml"); UserServiceImpl userServiceImpl= (UserServiceImpl) ac.getBean("userServiceImpl"); userServiceImpl.test(); } }
按上面的方法启动即可