• java笔记:自己动手写javaEE框架(五)Spring事务管理学习


    上篇文章写到了编程式事务的使用TransactionTemplate类的实现,TransactionTemplate类里的execute方法需要TransactionCallback接口实现类做参数,其接口的定义中只包含一个doInTransaction方法用于执行事务操作,上面的代码中使用匿名类的方式定义TransactionCallback接口实现,并且在doInTransaction()方法中定义事务处理代码。

    第五章 声明式事务管理

      我们使用最多的还是Spring声明式事务管理,其实所有Spring事务管理都是基于AOP来实现,而其中的声明式事务才是真正体现AOP全部优点的最佳应用,记得我以前参加面试一说到AOP我就提及声明式事务管理,哎,那个时候对AOP的理解也就仅限于此,太菜了。Spring声明式事务不涉及组件的依赖关系,使用它时候不需要编写任何代码,很大程度的节省了工作量,提高了工作效率。下面我在我的框架里面加入声明式事务,首先修改applicationContext.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-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

    <!-- 扫描该路径下的spring组件 -->
    <context:component-scan base-package="cn.com.sharpxiajun" />

    <!-- 读取资源文件 -->
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
    <list>
    <value>classpath:conf/constants.properties</value>
    </list>
    </property>
    </bean>

    <!-- 配置数据源 -->
    <!-- <bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="${db.driverClass}"/>
    <property name="jdbcUrl" value="${db.jdbcUrl}"/>
    <property name="user" value="${db.user}"/>
    <property name="password" value="${db.password}"/>
    </bean>
    -->

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${db.driverClass}"/>
    <property name="url" value="${db.jdbcUrl}"/>
    <property name="username" value="${db.user}"/>
    <property name="password" value="${db.password}"/>
    </bean>

    <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
    <property name="configLocation">
    <value>classpath:conf/SqlMapConfig.xml</value>
    </property>
    <property name="dataSource" ref="myDataSource"/>
    </bean>

    <bean id="sqlMapClientTemplate" class="org.springframework.orm.ibatis.SqlMapClientTemplate">
    <property name="sqlMapClient">
    <ref local="sqlMapClient"/>
    </property>
    </bean>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource">
    <ref local="myDataSource"/>
    </property>
    </bean>

    <!-- 声明式事务 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
    <tx:method name="get*" read-only="true" propagation="REQUIRED"/>
    <tx:method name="query*" read-only="true" propagation="REQUIRED"/>
    <tx:method name="find*" read-only="true" propagation="REQUIRED"/>
    <tx:method name="list*" read-only="true" propagation="REQUIRED"/>
    <tx:method name="search*" read-only="true" propagation="REQUIRED"/>
    <tx:method name="add*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
    <tx:method name="insert*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
    <tx:method name="del*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
    <tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
    <tx:method name="update*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
    <tx:method name="modify*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
    </tx:attributes>
    </tx:advice>

    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
    <property name="transactionManager">
    <ref bean="transactionManager"/>
    </property>
    <property name="propagationBehaviorName">
    <value>PROPAGATION_REQUIRED</value>
    </property>
    </bean>

    <!-- 将我自己定义的拦截器生成bean -->
    <bean id="methodServiceAdvisor" class="cn.com.sharpxiajun.common.aop.MethodServiceAdvisor"/>

    <aop:config>
    <!--配置规则,满足以下规则的将拦截,第一个*表示所有返回类型,第二个表示service包下的所有class,第三个表示所有方法-->
    <aop:pointcut id="baseServiceMethods" expression="execution(* cn.com.sharpxiajun.service.*.*(..))"/>
    <!-- 声明式事务 -->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="baseServiceMethods" />
    <!-- 符合上面规则的拦截器都会调用到methodServiceAdvisor -->
    <aop:advisor advice-ref="methodServiceAdvisor" pointcut-ref="baseServiceMethods"/>
    </aop:config>

    </beans>

      这里面添加了<tx:advice id="txAdvice" transaction-manager="transactionManager">,根据方法前缀不同绑定不同的事务策略,然后在<aop:config>里面添加<aop:advisor advice-ref="txAdvice" pointcut-ref="baseServiceMethods" />,那么service包下面类里的方法都会绑定相应的事务管理。在javaEE工程里面到底是DAO层绑定事务还是在Service层绑定事务?这个问题以前有同事问过我,我当时回答是都应该绑定,现在我会说最好只在Service层绑定事务。DAO层在我的理解里应该是数据库操作的映射,那是针对数据的原子操作,而Service层则是把这些各种数据库操作封装成一个业务操作单元,所以Service层的含义更符合事务本质,所以事务最好绑定在Service层。DAO层再绑定事务没必要,而且多余,甚至还会操作不必要的错误。

      接下来我修改USERS.xml映射文件,代码如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE sqlMap PUBLIC "-//ibatis.apache.org//DTD SQL Map 2.0//EN" "http://ibatis.apache.org/dtd/sql-map-2.dtd">
    <sqlMap namespace="USERS">
    <select id="queryUserList" parameterClass="java.util.Map" resultClass="java.util.HashMap">
    select t.username,t.password,t.enabled from users t
    </select>

    <!-- 新增用户信息 -->
    <insert id="addUsers" parameterClass="java.util.Map">
    insert into users
    <dynamic prepend="(">
    <isNotNull prepend="," property="username">
    username
    </isNotNull>
    <isNotNull prepend="," property="password">
    password
    </isNotNull>
    <isNotNull prepend="," property="enabled">
    enabled
    </isNotNull>
    )
    </dynamic>
    values
    <dynamic prepend="(">
    <isNotNull prepend="," property="username">
    #username:VARCHAR#
    </isNotNull>
    <isNotNull prepend="," property="password">
    #password:VARCHAR#
    </isNotNull>
    <isNotNull prepend="," property="enabled">
    #enabled:NUMBER#
    </isNotNull>
    )
    </dynamic>
    </insert>
    </sqlMap>

      修改cn.com.sharpxiajun.dao包下的接口UsersDao,代码如下:

    package cn.com.sharpxiajun.dao;

    import java.util.List;
    import java.util.Map;

    public interface UsersDao {

    public static final String QUERY_USERS_SQL = "USERS.queryUserList";
    public static final String ADD_USERS_SQL = "USERS.addUsers";

    public List<Map<String, Object>> queryUserList(Map<String, Object> map) throws Exception;

    public Object addUsers(Map<String, Object> map) throws Exception;

    }

      修改cn.com.sharpxiajun.dao.impl包下面UsersDaoImpl类,代码如下:

    package cn.com.sharpxiajun.dao.impl;

    import java.util.List;
    import java.util.Map;


    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Scope;
    import org.springframework.orm.ibatis.SqlMapClientTemplate;
    import org.springframework.stereotype.Repository;

    import cn.com.sharpxiajun.dao.UsersDao;

    @SuppressWarnings("unchecked")
    @Scope("prototype")
    @Repository("usersDao")
    public class UsersDaoImpl implements UsersDao {

    @Autowired
    @Qualifier("sqlMapClientTemplate")
    private SqlMapClientTemplate sqlMapClientTemplate = null;

    public List<Map<String, Object>> queryUserList(Map<String, Object> map)
    throws Exception {
    return sqlMapClientTemplate.queryForList(QUERY_USERS_SQL, map);
    }

    @Override
    public Object addUsers(Map<String, Object> map) throws Exception {
    return sqlMapClientTemplate.insert(ADD_USERS_SQL, map);
    }

    }

      修改cn.com.sharpxiajun.service包下接口UsersService,代码如下:

    package cn.com.sharpxiajun.service;

    import java.util.List;
    import java.util.Map;

    public interface UsersService {

    public List<Map<String, Object>> queryUsersList(Map<String, Object> map) throws Exception;

    public Object addUsers(Map<String, Object> map) throws Exception;

    }

      修改cn.com.sharpxiajun.service.impl包下类UsersServiceImpl,代码如下:

    package cn.com.sharpxiajun.service.impl;

    import java.util.List;
    import java.util.Map;


    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Scope;
    import org.springframework.stereotype.Service;

    import cn.com.sharpxiajun.dao.UsersDao;
    import cn.com.sharpxiajun.service.UsersService;

    @SuppressWarnings("unchecked")
    @Scope("prototype")
    @Service("userService")
    public class UsersServiceImpl implements UsersService {

    @Autowired
    @Qualifier("usersDao")
    private UsersDao usersDao = null;

    @Override
    public List<Map<String, Object>> queryUsersList(Map<String, Object> map)
    throws Exception {
    return usersDao.queryUserList(map);
    }

    @Override
    public Object addUsers(Map<String, Object> map) throws Exception {
    return usersDao.addUsers(map);
    }

    }

      修改cn.com.sharpxiajun.junittest.service包下测试类UsersServiceImplTest,代码如下:

    package cn.com.sharpxiajun.junittest.service;

    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;

    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.junit.After;
    import org.junit.Before;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    import org.springframework.test.context.transaction.TransactionConfiguration;

    import cn.com.sharpxiajun.service.UsersService;
    import cn.com.sharpxiajun.service.impl.UsersServiceImpl;

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations={"classpath:conf/applicationContext.xml"})
    @TransactionConfiguration(defaultRollback = false)
    public class UsersServiceImplTest extends
    AbstractTransactionalJUnit4SpringContextTests {

    private final static Log log = LogFactory.getLog(UsersServiceImpl.class);

    @Autowired
    private UsersService usersService = null;

    public UsersServiceImplTest()
    {
    System.out.println("初始化测试类....");
    }

    @Before
    public void setUp() throws Exception
    {
    System.out.println("测试开始....");
    }

    @After
    public void tearDown() throws Exception
    {
    System.out.println("测试结束!!");
    }

    @Test
    public void testQueryUserList()
    {
    Map<String, Object> map = new HashMap<String, Object>();
    map.put("username", "sharpxiajun");
    try {
    List<Map<String, Object>> list = usersService.queryUsersList(map);
    log.info(list);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    @Test
    public void testAddUsers()
    {
    Map<String, Object> map = new HashMap<String, Object>();
    map.put("username", "xiajun");
    map.put("password", "xiajun");
    map.put("enabled", 1);

    try {
    Object obj = usersService.addUsers(map);
    log.info("testAddUsers:" + obj);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    }

      运行测试类,结果如下:


      数据新增了,但是没有体现事务的作用,我前面写事务示例代码也没有考虑到如何表现事务的作用,为了体现事务作用,我在USERS.xml配置文件里添加新方法,代码如下:

        <!-- 修改用户信息 -->
    <update id="updateUsers" parameterClass="java.util.Map">
    update users set errinfo = 'error'
    </update>

      修改cn.com.sharpxiajun.dao包下的UsersDao接口,代码如下:

    package cn.com.sharpxiajun.dao;

    import java.util.List;
    import java.util.Map;

    public interface UsersDao {

    public static final String QUERY_USERS_SQL = "USERS.queryUserList";
    public static final String ADD_USERS_SQL = "USERS.addUsers";
    public static final String UPDATE_USERS_SQL = "USERS.updateUsers";

    public List<Map<String, Object>> queryUserList(Map<String, Object> map) throws Exception;

    public Object addUsers(Map<String, Object> map) throws Exception;

    public Object updateUsers(Map<String, Object> map) throws Exception;

    }

      修改cn.com.sharpxiajun.dao.impl包下的UsersDaoImpl类,代码如下:

    package cn.com.sharpxiajun.dao.impl;

    import java.util.List;
    import java.util.Map;


    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Scope;
    import org.springframework.orm.ibatis.SqlMapClientTemplate;
    import org.springframework.stereotype.Repository;

    import cn.com.sharpxiajun.dao.UsersDao;

    @SuppressWarnings("unchecked")
    @Scope("prototype")
    @Repository("usersDao")
    public class UsersDaoImpl implements UsersDao {

    @Autowired
    @Qualifier("sqlMapClientTemplate")
    private SqlMapClientTemplate sqlMapClientTemplate = null;

    public List<Map<String, Object>> queryUserList(Map<String, Object> map)
    throws Exception {
    return sqlMapClientTemplate.queryForList(QUERY_USERS_SQL, map);
    }

    @Override
    public Object addUsers(Map<String, Object> map) throws Exception {
    return sqlMapClientTemplate.insert(ADD_USERS_SQL, map);
    }

    @Override
    public Object updateUsers(Map<String, Object> map) throws Exception {
    return sqlMapClientTemplate.update(UPDATE_USERS_SQL, map);
    }

    }

      在cn.com.sharpxiajun.service包的UsersService接口里添加方法:

    public void updateUsers(Map<String, Object> map) throws Exception;

      在cn.com.sharpxiajun.service.impl下的UsersServiceImpl类里实现该方法,代码如下:

        @Override
    public void updateUsers(Map<String, Object> map) throws Exception {
    usersDao.addUsers(map);
    usersDao.updateUsers(map);
    }

      从上面代码可以看出USERS.xml的update方法是不能被执行,程序一定会报出异常,如果方法绑定了事务,那么usersDao.addUsers(map);操作会被回滚掉,也就是新增操作不会成功,下面我在cn.com.sharpxiajun.junittest.service包下的UsersServiceImplTest类里添加新的测试方法,代码如下:

        @Test
    public void testUpdateUsers()
    {
    Map<String, Object> map = new HashMap<String, Object>();
    map.put("username", "sharp");
    map.put("password", "sharp");
    map.put("enabled", 1);

    try {
    usersService.updateUsers(map);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    (注意:最好把测试新增方法的代码注释掉)

      运行测试类,控制台会打印出下面异常:

    2011-10-25 23:10:59  SQLErrorCodesFactory - SQLErrorCodes loaded: [DB2, Derby, H2, HSQL, Informix, MS-SQL, MySQL, Oracle, PostgreSQL, Sybase]
    org.springframework.jdbc.BadSqlGrammarException: SqlMapClient operation; bad SQL grammar []; nested exception is com.ibatis.common.jdbc.exception.NestedSQLException:
    --- The error occurred in cn/com/sharpxiajun/dao/sqlmap/USERS.xml.
    --- The error occurred while applying a parameter map.
    --- Check the USERS.updateUsers-InlineParameterMap.
    --- Check the statement (update failed).
    --- Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'errinfo' in 'field list'
    at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:233)
    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72)
    at org.springframework.orm.ibatis.SqlMapClientTemplate.execute(SqlMapClientTemplate.java:203)
    at org.springframework.orm.ibatis.SqlMapClientTemplate.update(SqlMapClientTemplate.java:378)
    at cn.com.sharpxiajun.dao.impl.UsersDaoImpl.updateUsers(UsersDaoImpl.java:36)
    at cn.com.sharpxiajun.service.impl.UsersServiceImpl.updateUsers(UsersServiceImpl.java:45)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:309)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
    at cn.com.sharpxiajun.common.aop.MethodServiceAdvisor.invoke(MethodServiceAdvisor.java:31)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:89)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
    at $Proxy18.updateUsers(Unknown Source)
    at cn.com.sharpxiajun.junittest.service.UsersServiceImplTest.testUpdateUsers(UsersServiceImplTest.java:103)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:240)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:180)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
    Caused by: com.ibatis.common.jdbc.exception.NestedSQLException:
    --- The error occurred in cn/com/sharpxiajun/dao/sqlmap/USERS.xml.
    --- The error occurred while applying a parameter map.
    --- Check the USERS.updateUsers-InlineParameterMap.
    --- Check the statement (update failed).
    --- Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'errinfo' in 'field list'
    at com.ibatis.sqlmap.engine.mapping.statement.MappedStatement.executeUpdate(MappedStatement.java:107)
    at com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate.update(SqlMapExecutorDelegate.java:457)
    at com.ibatis.sqlmap.engine.impl.SqlMapSessionImpl.update(SqlMapSessionImpl.java:90)
    at org.springframework.orm.ibatis.SqlMapClientTemplate$9.doInSqlMapClient(SqlMapClientTemplate.java:380)
    at org.springframework.orm.ibatis.SqlMapClientTemplate$9.doInSqlMapClient(SqlMapClientTemplate.java:1)
    at org.springframework.orm.ibatis.SqlMapClientTemplate.execute(SqlMapClientTemplate.java:200)
    ... 49 more
    Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'errinfo' in 'field list'
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
    at com.mysql.jdbc.Util.handleNewInstance(Util.java:406)
    at com.mysql.jdbc.Util.getInstance(Util.java:381)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1031)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:957)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3376)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3308)
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1837)
    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1961)
    at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2543)
    at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1737)
    at com.mysql.jdbc.PreparedStatement.execute(PreparedStatement.java:998)
    at org.apache.commons.dbcp.DelegatingPreparedStatement.execute(DelegatingPreparedStatement.java:169)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at com.ibatis.common.jdbc.logging.PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:62)
    at $Proxy21.execute(Unknown Source)
    at com.ibatis.sqlmap.engine.execution.SqlExecutor.executeUpdate(SqlExecutor.java:80)
    at com.ibatis.sqlmap.engine.mapping.statement.MappedStatement.sqlExecuteUpdate(MappedStatement.java:216)
    at com.ibatis.sqlmap.engine.mapping.statement.MappedStatement.executeUpdate(MappedStatement.java:94)
    ... 54 more
    测试结束!!
    2011-10-25 23:10:59 TestContextManager - Caught exception while allowing TestExecutionListener [org.springframework.test.context.transaction.TransactionalTestExecutionListener@6798eb] to process 'after' execution for test: method [public void cn.com.sharpxiajun.junittest.service.UsersServiceImplTest.testUpdateUsers()], instance [cn.com.sharpxiajun.junittest.service.UsersServiceImplTest@168989e], exception [null]
    org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:717)
    at org.springframework.test.context.transaction.TransactionalTestExecutionListener$TransactionContext.endTransaction(TransactionalTestExecutionListener.java:516)
    at org.springframework.test.context.transaction.TransactionalTestExecutionListener.endTransaction(TransactionalTestExecutionListener.java:291)
    at org.springframework.test.context.transaction.TransactionalTestExecutionListener.afterTestMethod(TransactionalTestExecutionListener.java:184)
    at org.springframework.test.context.TestContextManager.afterTestMethod(TestContextManager.java:406)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:90)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:240)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:180)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)

      结果如下:

      没有新的记录被添加,事务被成功绑定了~~~。

      那么如果我们写在Service里面的方法没有用applicationContext.xml里面设置的前缀,效果会如何了?

      我在cn.com.sharpxiajun.service包的UsersService接口下添加新方法,代码如下:

    public void noTransactionMethod(Map<String, Object> map) throws Exception;

      在cn.com.sharpxiajun.service.impl包里UsersServiceImpl类实现该方法,代码如下:

        @Override
    public void noTransactionMethod(Map<String, Object> map) throws Exception {
    usersDao.addUsers(map);
    usersDao.updateUsers(map);

    }

      最后在cn.com.sharpxiajun.junittest.service包下,修改UsersServiceImplTest测试类,代码如下:

    //    @Test
    // public void testAddUsers()
    // {
    // Map<String, Object> map = new HashMap<String, Object>();
    // map.put("username", "xiajun");
    // map.put("password", "xiajun");
    // map.put("enabled", 1);
    //
    // try {
    // Object obj = usersService.addUsers(map);
    // log.info("testAddUsers:" + obj);
    // } catch (Exception e) {
    // e.printStackTrace();
    // }
    // }

    @Test
    public void testNoTransactionMethod()
    {
    Map<String, Object> map = new HashMap<String, Object>();
    map.put("username", "sharp");
    map.put("password", "sharp");
    map.put("enabled", 1);

    try {
    usersService.noTransactionMethod(map);
    } catch (Exception e) {
    e.printStackTrace();
    }
    }

    // @Test
    // public void testUpdateUsers()
    // {
    // Map<String, Object> map = new HashMap<String, Object>();
    // map.put("username", "sharp");
    // map.put("password", "sharp");
    // map.put("enabled", 1);
    //
    // try {
    // usersService.updateUsers(map);
    // } catch (Exception e) {
    // e.printStackTrace();
    // }
    // }

      运行测试类,控制台打印出的异常是:

    2011-10-25 23:28:00  SQLErrorCodesFactory - SQLErrorCodes loaded: [DB2, Derby, H2, HSQL, Informix, MS-SQL, MySQL, Oracle, PostgreSQL, Sybase]
    org.springframework.jdbc.BadSqlGrammarException: SqlMapClient operation; bad SQL grammar []; nested exception is com.ibatis.common.jdbc.exception.NestedSQLException:
    --- The error occurred in cn/com/sharpxiajun/dao/sqlmap/USERS.xml.
    --- The error occurred while applying a parameter map.
    --- Check the USERS.updateUsers-InlineParameterMap.
    --- Check the statement (update failed).
    --- Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'errinfo' in 'field list'
    at org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(SQLErrorCodeSQLExceptionTranslator.java:233)
    at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:72)
    at org.springframework.orm.ibatis.SqlMapClientTemplate.execute(SqlMapClientTemplate.java:203)
    at org.springframework.orm.ibatis.SqlMapClientTemplate.update(SqlMapClientTemplate.java:378)
    at cn.com.sharpxiajun.dao.impl.UsersDaoImpl.updateUsers(UsersDaoImpl.java:36)
    at cn.com.sharpxiajun.service.impl.UsersServiceImpl.noTransactionMethod(UsersServiceImpl.java:38)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:309)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
    at cn.com.sharpxiajun.common.aop.MethodServiceAdvisor.invoke(MethodServiceAdvisor.java:31)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:89)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
    at $Proxy18.noTransactionMethod(Unknown Source)
    at cn.com.sharpxiajun.junittest.service.UsersServiceImplTest.testNoTransactionMethod(UsersServiceImplTest.java:88)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)
    at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:28)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)
    at org.junit.internal.runners.statements.RunAfters.evaluate(RunAfters.java:31)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:240)
    at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:236)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:180)
    at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)
    Caused by: com.ibatis.common.jdbc.exception.NestedSQLException:
    --- The error occurred in cn/com/sharpxiajun/dao/sqlmap/USERS.xml.
    --- The error occurred while applying a parameter map.
    --- Check the USERS.updateUsers-InlineParameterMap.
    --- Check the statement (update failed).
    --- Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'errinfo' in 'field list'
    at com.ibatis.sqlmap.engine.mapping.statement.MappedStatement.executeUpdate(MappedStatement.java:107)
    at com.ibatis.sqlmap.engine.impl.SqlMapExecutorDelegate.update(SqlMapExecutorDelegate.java:457)
    at com.ibatis.sqlmap.engine.impl.SqlMapSessionImpl.update(SqlMapSessionImpl.java:90)
    at org.springframework.orm.ibatis.SqlMapClientTemplate$9.doInSqlMapClient(SqlMapClientTemplate.java:380)
    at org.springframework.orm.ibatis.SqlMapClientTemplate$9.doInSqlMapClient(SqlMapClientTemplate.java:1)
    at org.springframework.orm.ibatis.SqlMapClientTemplate.execute(SqlMapClientTemplate.java:200)
    ... 49 more
    Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'errinfo' in 'field list'
    at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
    at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
    at java.lang.reflect.Constructor.newInstance(Constructor.java:513)
    at com.mysql.jdbc.Util.handleNewInstance(Util.java:406)
    at com.mysql.jdbc.Util.getInstance(Util.java:381)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:1031)
    at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:957)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3376)
    at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3308)
    at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1837)
    at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1961)
    at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2543)
    at com.mysql.jdbc.PreparedStatement.executeInternal(PreparedStatement.java:1737)
    at com.mysql.jdbc.PreparedStatement.execute(PreparedStatement.java:998)
    at org.apache.commons.dbcp.DelegatingPreparedStatement.execute(DelegatingPreparedStatement.java:169)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at com.ibatis.common.jdbc.logging.PreparedStatementLogProxy.invoke(PreparedStatementLogProxy.java:62)
    at $Proxy21.execute(Unknown Source)
    at com.ibatis.sqlmap.engine.execution.SqlExecutor.executeUpdate(SqlExecutor.java:80)
    at com.ibatis.sqlmap.engine.mapping.statement.MappedStatement.sqlExecuteUpdate(MappedStatement.java:216)
    at com.ibatis.sqlmap.engine.mapping.statement.MappedStatement.executeUpdate(MappedStatement.java:94)
    ... 54 more

      我们发现这里面少了事务回滚的异常,数据库查询结果如下:

      有记录被新增了~~~

    第六章 通过注解绑定事务管理

      声明式事务咋看一下还是十分简单的,但是Spring引入注解后还有更加简单的配置,请看修改后新的applicationContext.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-2.5.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">

    <!-- 扫描该路径下的spring组件 -->
    <context:component-scan base-package="cn.com.sharpxiajun" />

    <!-- 读取资源文件 -->
    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="locations">
    <list>
    <value>classpath:conf/constants.properties</value>
    </list>
    </property>
    </bean>

    <!-- 配置数据源 -->
    <!-- <bean id="myDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="${db.driverClass}"/>
    <property name="jdbcUrl" value="${db.jdbcUrl}"/>
    <property name="user" value="${db.user}"/>
    <property name="password" value="${db.password}"/>
    </bean>
    -->

    <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${db.driverClass}"/>
    <property name="url" value="${db.jdbcUrl}"/>
    <property name="username" value="${db.user}"/>
    <property name="password" value="${db.password}"/>
    </bean>

    <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean">
    <property name="configLocation">
    <value>classpath:conf/SqlMapConfig.xml</value>
    </property>
    <property name="dataSource" ref="myDataSource"/>
    </bean>

    <bean id="sqlMapClientTemplate" class="org.springframework.orm.ibatis.SqlMapClientTemplate">
    <property name="sqlMapClient">
    <ref local="sqlMapClient"/>
    </property>
    </bean>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource">
    <ref local="myDataSource"/>
    </property>
    </bean>

    <!-- 声明式事务 -->
    <!-- <tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
    <tx:method name="get*" read-only="true" propagation="REQUIRED"/>
    <tx:method name="query*" read-only="true" propagation="REQUIRED"/>
    <tx:method name="find*" read-only="true" propagation="REQUIRED"/>
    <tx:method name="list*" read-only="true" propagation="REQUIRED"/>
    <tx:method name="search*" read-only="true" propagation="REQUIRED"/>
    <tx:method name="add*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
    <tx:method name="insert*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
    <tx:method name="del*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
    <tx:method name="save*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
    <tx:method name="update*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
    <tx:method name="modify*" propagation="REQUIRED" isolation="READ_COMMITTED"/>
    </tx:attributes>
    </tx:advice>
    -->

    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
    <property name="transactionManager">
    <ref bean="transactionManager"/>
    </property>
    <property name="propagationBehaviorName">
    <value>PROPAGATION_REQUIRED</value>
    </property>
    </bean>

    <!-- 将我自己定义的拦截器生成bean -->
    <bean id="methodServiceAdvisor" class="cn.com.sharpxiajun.common.aop.MethodServiceAdvisor"/>

    <aop:config>
    <!--配置规则,满足以下规则的将拦截,第一个*表示所有返回类型,第二个表示service包下的所有class,第三个表示所有方法-->
    <aop:pointcut id="baseServiceMethods" expression="execution(* cn.com.sharpxiajun.service.*.*(..))"/>
    <!-- 声明式事务 -->
    <!-- <aop:advisor advice-ref="txAdvice" pointcut-ref="baseServiceMethods" />-->
    <!-- 符合上面规则的拦截器都会调用到methodServiceAdvisor -->
    <aop:advisor advice-ref="methodServiceAdvisor" pointcut-ref="baseServiceMethods"/>
    </aop:config>

    <tx:annotation-driven transaction-manager="transactionManager"/>

    </beans>

      配置文件里我把原来的声明式配置内容注释掉了,然后加入了<tx:annotation-driven transaction-manager="transactionManager"/>。

      然后在cn.com.sharpxiajun.service.impl包下的UsersServiceImpl类加入@Transactional注解,代码如下:

    @SuppressWarnings("unchecked")
    @Scope("prototype")
    @Transactional
    @Service("userService")
    public class UsersServiceImpl implements UsersService {
    ......................................................

      将数据库里的数据都清除掉,运行测试类UsersServiceImplTest,测试service下的noTransactionMethod方法和updateUsers方法,结果显示数据都没有新增成功,这就说明事务绑定是成功了,这个简单吧。

      总结下了:事务很重要,我希望我这两篇博客能给童鞋们一点帮助。

      下一篇文章将把struts2框架引入到我写的框架。

  • 相关阅读:
    com.mongodb.MongoException$CursorNotFound: cursor not found on server异常处理
    Oracle中暂时表空间的清理
    java——数组
    二分查找的感悟
    【转】Linux时间函数之gettimeofday()函数之使用方法
    【转】《APUE》第三章笔记(4)及习题3-2
    【转】APUE习题4.6---测试lseek作用
    【转】linux下a.out >outfile 2>&1重定向问题
    【转】./a.out 2>&1 > outfile
    【转】apue《UNIX环境高级编程第三版》第一章答案详解
  • 原文地址:https://www.cnblogs.com/sharpxiajun/p/2224621.html
Copyright © 2020-2023  润新知