zoukankan      html  css  js  c++  java
  • (02)验证Spring的事务及其7种传播机制真实案例

      原文:

      https://blog.csdn.net/soonfly/article/details/70305683

      https://www.cnblogs.com/dennyzhangdd/p/9602670.html

      https://blog.csdn.net/fly910905/article/details/80000242

      说起Spring的事务,仿佛是很熟悉的老朋友了,然鹅。。。现实却很残酷。。。起初我以 Spring、Mybatis、druid、Mysql尝试,发现其中一个问题,无论数据源的defaultAutoCommit设置为true或者false,事务总会自动提交。确定配置无误后,发现网上有一种说法是把数据库的autocommit设置为OFF,即关闭数据库的自动提交,且不说这样是否可以,这种方法本身就有问题。因为在代码中获取到一个Connection,执行commit即可提交,不执行commit就不会提交,完全可以由代码控制,去设置数据库本身,这很不合理。经过一番周折还是没有搞定这个问题。

      无奈之下我把Mybatis换成JdbcTemplate,终于正常了。下面基于Spring+JdbcTemplate+druid+Mysql说说事务。

      1、事务、事务传播机制的简单说明

      事务是一个单体行为,只有提交了事务,数据才会保存到数据库,否则不会保存到数据库中。事务传播行要求至少有两个东西,才可以发生传播。指的是当一个事务方法被另一个事务方法调用时,这个被调用方法的事务方法应该如何进行。例如:methodA事务方法调用methodB事务方法时,methodB是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。

      2、defaultAutoCommit与Transactional的关系

      配置数据源时参数defaultAutoCommit设置为ture、false代表自动、不自动提交。Transactional注解也控制事务,他们有什么关系?下面用例子说明。

      (1)defaultAutoCommit为false,不使用Transactional注解。结论:不会提交

    <property name="defaultAutoCommit" value="false" />
    public void save() {
      testDao.save();
    }

       (2)defaultAutoCommit为false,使用Transactional注解。结论:会提交

    <property name="defaultAutoCommit" value="false" />
    @Transactional
    public void save() {   testDao.save(); }

      (3)defaultAutoCommit为true,不使用Transactional注解。结论:会提交

    <property name="defaultAutoCommit" value="true" />
    public void save() {
      testDao.save();
    }

      (4)defaultAutoCommit为true,使用Transactional注解。结论:会提交

    <property name="defaultAutoCommit" value="true" />
    @Transactional
    public void save() {
      testDao.save();
    }

      总结:只要defaultAutoCommit或者Transactional有一项设置为可提交即可。

      3、Transactional与异常自动回滚的关系

      在项目中希望当方法产生异常时自动回滚事务,下面我们在defaultAutoCommit设置为false的情况下进行验证

    <property name="defaultAutoCommit" value="false" />

      (1)使用Transactional的默认配置,抛出检查型异常。事务不会回滚

    @Transactional
    public void save () throws Exception {
      testDao.save();
      throw new ClassNotFoundException();
    }

      (2)使用Transactional的默认配置,抛出运行时异常。事务会回滚

    @Transactional
    public void save (){
      testDao.save();
      throw new RuntimeException();
    }

      (3)使用Transactional注解,指定rollbackFor为抛出的异常或其父类时,检查型异常会回滚

    @Transactional(rollbackFor=Exception.class)
    public void save () throws Exception {
      testDao.save();   
    throw new ClassNotFoundException(); }

      (4)使用Transactional注解,指定rollbackFor不是抛出的异常或其父类时,运行时异常会回滚(运行时异常与rollbackFor无关,肯定回滚)

    @Transactional(rollbackFor=FileNotFoundException.class)
    public void save () throws Exception {
      testDao.save();
      throw new RuntimeException();
    }

      (5)使用Transactional注解,捕获异常,事务不会回滚

    @Transactional
    public void save () throws Exception {
      try {
        testDao.save();
        throw new RuntimeException();
      } catch (Exception e) {
      // TODO: handle exception
      }
    }

    @Transactional
    public void save () throws Exception {
      try {
        testDao.save();
        throw new ClassNotFoundException();
      } catch (Exception e) {
        // TODO: handle exception
      }
    }

      (6)捕获的异常需要手动回滚,手动回滚时检查型异常可以不指定rollbackFor

    @Transactional
    public void save () {
      try {
        testDao.save();
        throw new ClassNotFoundException();
      } catch (Exception e) {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 
      }
    }

    @Transactional(rollbackFor=FileNotFoundException.class)
    public void save () {
      try {
        testDao.save();
        throw new ClassNotFoundException();
      } catch (Exception e) {
      TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 
      }
    }

    @Transactional
    public void save () {
      try {
        testDao.save();
        throw new RuntimeException();
      } catch (Exception e) {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 
      }
    }

      (7) Transactional要加在主动(直接)调用的方法上面,以下事务不会提交,没有开启事务(spring容器管理的类直接调用test)

    public void test(){
        save();
    }
    
    @Transactional
    public void save () {
        try {
            testDao.save();
            throw new RuntimeException();
        } catch (Exception e) {
        }
    }    

      4、spring中的事务传播行为

      spring中共有7种事务传播行为,分别介绍如下:

      (1)PROPAGATION_REQUIRED:如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。

      方法A加注解,方法B也加注解,当方法A运行时会开启事务A,调用方法B时,方法B也加入到事务A中

     @Transactional(propagation = Propagation.REQUIRED)
     public void methodA() {
       methodB();
       testDao.methodA();
     }
    
     @Transactional(propagation = Propagation.REQUIRED)
     public void methodB() {
       testDao.methodB();
     }

       如上图,总共开启了一个事务。

      (2)PROPAGATION_SUPPORTS:如果存在一个事务,支持当前事务,如果没有事务,则不会开启事务。

    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA() {
      methodB();
      testDao.methodA();
    }
    
    @Transactional(propagation = Propagation.SUPPORTS)
    public void methodB() {
      testDao.methodB();
    }

      如果直接调用methodA,methodA会开启一个事务,methodA调用methodB,则methodB支持当前methodA开启的事务,如下图:

      如果直接调用methodB,不会开启事务,如下图:

      如果直接调用methodA,由于methodA是SUPPORTS,不会开始事务,methodB不是直接调用,也不会开启事务

    @Transactional(propagation = Propagation.SUPPORTS)
    public void methodA() {
      methodB();
      testDao.methodA();
    } @Transactional(propagation
    = Propagation.REQUIRED)   public void methodB() {   testDao.methodB(); }

      (3)PROPAGATION_MANDATORY:必须在一个事务中运行,否则报异常

    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA() {
    methodB();
    testDao.methodA();
    }

    @Transactional(propagation
    = Propagation.MANDATORY)   public void methodB() {   testDao.methodB(); }

      直接调用methodA,开启一个事务,methodB也在该事务中运行

      直接调用methodB,报异常 No existing transaction found for transaction marked with propagation 'mandatory'

     @Transactional(propagation = Propagation.MANDATORY)
     public void methodB() {
       testDao.methodB();
     }

      (4)PROPAGATION_REQUIRES_NEW:开启一个新事务。如果一个事务已经存在,则先将存在的事务挂起,执行完这个新事务,再执行挂起的事务,两个事务的成功或失败没有联系。

    @Transactional(propagation = Propagation.REQUIRED)
    public void methodA() {
      methodB();
      testDao.methodA();
    }
    @Transactional(propagation
    = Propagation.REQUIRES_NEW) public void methodB() {   testDao.methodB(); }

      从上图中看到,并没有挂起旧事务,先执行新事务,因为只有使用JtaTransactionManager作为事务管理器时才生效。后面再研究。。。

      (5)PROPAGATION_NOT_SUPPORTED:在非事务中运行。如果有事务正在运行,他将在运行期被挂起,直到这个事务提交或者回滚才恢复执行。

     @Transactional(propagation = Propagation.REQUIRED)
     public void methodA() {
       methodB();
        testDao.methodA();
     }
    
     @Transactional(propagation = Propagation.NOT_SUPPORTED)
     public void methodB() {
       testDao.methodB();
     }

      直接调用methodA,运行到methodB,事务应该挂起,即methodB对应的数据不会保存到数据库。

      但上图与预期的不一致,因为也需要JtaTransactionManager作为事务管理器 。

      直接调用methodB不会开启事务,可以自己尝试一下。

      (6)PROPAGATION_NEVER:总是非事务地执行,如果存在一个活动事务,则抛出异常。

    @Transactional(propagation = Propagation.NEVER)
    public void methodB() {
      testDao.methodB();
    }

      直接调用methodB,不会开启事务

     @Transactional(propagation = Propagation.REQUIRED)
     public void methodA() {
       methodB();
       testDao.methodA();
     }
    
     @Transactional(propagation = Propagation.NEVER)
     public void methodB() {
       testDao.methodB();
     }

      直接调用methodA,报异常,发现下面日志没有报异常,,,是版本问题还是我的理解有误???先留个疑问吧

      (7) PROPAGATION_NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。 如果没有活动事务, 则按PROPAGATION_REQUIRED属性执行。

      附 相关配置文件和代码

      pom.xml

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>com.sl</groupId>
      <artifactId>spring-web-transaction</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      <packaging>war</packaging>
      
      <!-- 项目属性 -->
      <properties>
          <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <tomcat.version>2.2</tomcat.version>
        
          <spring.version>5.1.0.RELEASE</spring.version>
          <!-- 声明式事务 -->
          <aspectjweaver.version>1.7.4</aspectjweaver.version>
          <druid.version>1.1.11</druid.version>
          <mysql.driver.version>5.1.30</mysql.driver.version>
          
        <jackson.version>2.5.4</jackson.version>
          <slf4j.version>1.7.7</slf4j.version>
      </properties>
      
      <!-- 依赖 -->
      <dependencies>
        
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        
        <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>${aspectjweaver.version}</version>
            </dependency>
        
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
          
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${druid.version}</version>
        </dependency>
        
        <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>${mysql.driver.version}</version>
                <scope>runtime</scope>
            </dependency>
        
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        
      </dependencies>
      
      <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-war-plugin</artifactId>
                    <version>2.4</version>
                    <configuration>
                        <webappDirectory>${project.build.directory}/${project.artifactId}</webappDirectory>
                        <warName>${project.artifactId}</warName>
                    </configuration>
                </plugin>    
                <!-- tomcat7插件 -->
                <plugin>
                    <groupId>org.apache.tomcat.maven</groupId>
                    <artifactId>tomcat7-maven-plugin</artifactId>
                    <version>${tomcat.version}</version> 
                    <configuration>
                        <port>8080</port>
                        <path>/${project.artifactId}</path>
                        <uriEncoding>${project.build.sourceEncoding}</uriEncoding>
                    </configuration>
                </plugin>    
                
            </plugins>
        </build>
      
    </project>
    View Code

      spring-context.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:aop="http://www.springframework.org/schema/aop"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xmlns:task="http://www.springframework.org/schema/task"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
                      http://www.springframework.org/schema/beans/spring-beans.xsd
                      http://www.springframework.org/schema/aop
                      http://www.springframework.org/schema/aop/spring-aop.xsd
                      http://www.springframework.org/schema/context
                      http://www.springframework.org/schema/context/spring-context.xsd
                      http://www.springframework.org/schema/tx
                      http://www.springframework.org/schema/tx/spring-tx.xsd
                      http://www.springframework.org/schema/task  http://www.springframework.org/schema/task/spring-task-3.1.xsd
                      ">
                     
        <!-- 启用注解 -->
            <context:component-scan base-package="com.sl.*">
                <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
            </context:component-scan>
             <tx:annotation-driven transaction-manager="transactionManager"/>  
    
        <!--读取配置文件;可以读取多个-->
        <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <property name="locations">
                <list>
                    <value>classpath:db.properties</value>
                </list>
            </property>
        </bean>
        
        <!-- 阿里 druid数据库连接池 -->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" destroy-method="close">
            <!-- 数据库基本信息配置 -->
            <property name="url" value="${url}" />
            <property name="username" value="${username}" />
            <property name="password" value="${password}" />
            <property name="driverClassName" value="${driverClassName}" />
            <property name="defaultAutoCommit" value="false" />
            <property name="filters" value="${filters}" />
            <!-- 最大并发连接数 -->
            <property name="maxActive" value="${maxActive}" />
            <!-- 初始化连接数量 -->
            <property name="initialSize" value="${initialSize}" />
            <!-- 配置获取连接等待超时的时间 -->
            <property name="maxWait" value="${maxWait}" />
            <!-- 最小空闲连接数 -->
            <property name="minIdle" value="${minIdle}" />
            <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
            <property name="timeBetweenEvictionRunsMillis" value="${timeBetweenEvictionRunsMillis}" />
            <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
            <property name="minEvictableIdleTimeMillis" value="${minEvictableIdleTimeMillis}" />
            <property name="validationQuery" value="${validationQuery}" />
            <property name="testWhileIdle" value="${testWhileIdle}" />
            <property name="testOnBorrow" value="${testOnBorrow}" />
            <property name="testOnReturn" value="${testOnReturn}" />
            <property name="maxOpenPreparedStatements" value="${maxOpenPreparedStatements}" />
            <!-- 打开removeAbandoned功能 -->
            <property name="removeAbandoned" value="${removeAbandoned}" />
            <!-- 1800秒,也就是30分钟 -->
            <property name="removeAbandonedTimeout" value="${removeAbandonedTimeout}" />
            <!-- 关闭abanded连接时输出错误日志 -->
            <property name="logAbandoned" value="${logAbandoned}" />
        </bean>
        
            <!--配置数据库连接-->
        <bean name="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> 
     <property name="dataSource" ref="dataSource"></property>
    </bean>
       
    
    </beans>
    View Code

      spring-mvc.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:mvc="http://www.springframework.org/schema/mvc"
           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/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
    
        <context:component-scan base-package="com.sl.controller" />
         <mvc:default-servlet-handler/>
    
        <!-- 对静态资源文件的访问  restful-->
    
        <!--  -->
        <mvc:annotation-driven>
        </mvc:annotation-driven>
        
        <!-- 配置SpringMVC的视图解析器 -->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/"/>
            <property name="suffix" value=".jsp"/>
        </bean>
    
    
    </beans>
    View Code

      log4j.properties

    # DEBUG,INFO,WARN,ERROR,FATAL
    log4j.rootLogger=DEBUG,CONSOLE,FILE
    
    log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
    log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
    log4j.appender.CONSOLE.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss} %C{1}@(%F:%L):%m%n
    
    log4j.appender.FILE=org.apache.log4j.DailyRollingFileAppender
    log4j.appender.FILE.File=${catalina.base}/logs/spring-web.log
    log4j.appender.FILE.Encoding=utf-8
    log4j.appender.FILE.DatePattern='.'yyyy-MM-dd
    log4j.appender.FILE.layout=org.apache.log4j.PatternLayout
    log4j.appender.FILE.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss} %C{1}@(%F:%L):%m%n
    
    log4j.logger.com.mybatis=DEBUG
    log4j.logger.com.mybatis.common.jdbc.SimpleDataSource=DEBUG 
    log4j.logger.com.mybatis.common.jdbc.ScriptRunner=DEBUG 
    log4j.logger.com.mybatis.sqlmap.engine.impl.SqlMapClientDelegate=DEBUG 
    log4j.logger.java.sql.Connection=DEBUG
    log4j.logger.java.sql.Statement=DEBUG
    log4j.logger.java.sql.PreparedStatement=DEBUG
    log4j.logger.java.sql.ResultSet=DEBUG
    View Code

      db.properties

    url:jdbc:mysql://localhost:3309/mytest?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8 
    driverClassName:com.mysql.jdbc.Driver
    username:root
    password:123456
           
    filters:stat
       
    maxActive:20
    initialSize:1
    maxWait:60000
    minIdle:10
    maxIdle:15
       
    timeBetweenEvictionRunsMillis:60000
    minEvictableIdleTimeMillis:300000
       
    validationQuery:SELECT 'x'
    testWhileIdle:true
    testOnBorrow:false
    testOnReturn:false
    
    maxOpenPreparedStatements:20
    removeAbandoned:true
    removeAbandonedTimeout:1800
    logAbandoned:true
    View Code

      TestController.java

    package com.sl.controller;
    
    import java.util.List;
    import java.util.Map;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    import com.sl.service.TestService;
    
    @Controller
    @RequestMapping("/test")
    public class TestController {
    
        @Autowired
        private TestService testService;
        
        @RequestMapping("/save")
        @ResponseBody
        public void save(){
            //testService.save();
            testService.methodA();
        }
        
        @RequestMapping("/del")
        @ResponseBody
        public void del() throws Exception {
            testService.del();
        }
        
        @RequestMapping("/get")
        @ResponseBody
        public String get(){
            String str= "...";
            List<Map<String, Object>> list = testService.get();
            for(Map<String, Object> map : list) {
                str = map.get("name").toString();
            }
            return str;
        }
        
        @RequestMapping("/update")
        @ResponseBody
        public void update() throws Exception {
            testService.update();
        }
    }
    View Code  

      TestService.java

    package com.sl.service;
    
    import java.util.List;
    import java.util.Map;
    
    public interface TestService {
        public void save() throws Exception;
        public void del();
        public void test();
        public List<Map<String, Object>> get();
        public void update();
        public void methodA();
        public void methodB();
    }
    View Code

      TestServiceImpl.java

    package com.sl.service.impl;
    
    import java.util.List;
    import java.util.Map;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Propagation;
    import org.springframework.transaction.annotation.Transactional;
    
    import com.sl.dao.TestDao;
    import com.sl.service.TestService;
    
    @Service
    public class TestServiceImpl implements TestService{
    
    private static Logger logger=LoggerFactory.getLogger(TestServiceImpl.class);
    
        @Autowired
        private TestDao testDao;
        
    
        public void test(){
            save();
        }
    
        @Transactional
        public void save () {
            try {
                testDao.save();
                throw new RuntimeException();
            } catch (Exception e) {
            }
        }
        
        @Transactional(propagation = Propagation.REQUIRED)
        public void methodA() {
         methodB();
         testDao.methodA();
        }
    
        @Transactional(propagation = Propagation.NEVER)
        public void methodB() {
            testDao.methodB();
        }
    
        @Override
        @Transactional(propagation=Propagation.NEVER)
        public void del() {
            // TODO Auto-generated method stub
            testDao.del();
        }
    
        @Override
        public List<Map<String, Object>> get() {
            // TODO Auto-generated method stub
            return testDao.get();
        }
    
        @Override
        @Transactional(propagation=Propagation.REQUIRED)
        public void update() {
            // TODO Auto-generated method stub
            testDao.update();
        }
    
    }
    View Code

      TestDao.java

    package com.sl.dao;
    
    import java.sql.SQLException;
    import java.util.List;
    import java.util.Map;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Repository;
    
    @Repository
    public class TestDao {
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
        
        public void save() {
            String sql="insert into t_testa(name) values('11111')";
            jdbcTemplate.execute(sql);
        }
        
        public void methodA() {
            String sql="insert into t_testa(name) values('11111')";
            jdbcTemplate.execute(sql);
        }
        
        public void methodB() {
            String sql="insert into t_testb(name) values('11111')";
            jdbcTemplate.execute(sql);
        }
        
        public void del() {
            String sql="delete from t_testa";
            jdbcTemplate.execute(sql);
        }
        
        public List<Map<String, Object>> get() {
            String sql="select * from t_testa";
            List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);
            return list;
        }
        
        public void update() {
            String sql="update t_testa set name = 'asdfg'";
            jdbcTemplate.execute(sql);
        }
    }
    View Code
  • 相关阅读:
    Vasya and Endless Credits CodeForces
    Dreamoon and Strings CodeForces
    Online Meeting CodeForces
    数塔取数 基础dp
    1001 数组中和等于K的数对 1090 3个数和为0
    1091 线段的重叠
    51nod 最小周长
    走格子 51nod
    1289 大鱼吃小鱼
    POJ 1979 Red and Black
  • 原文地址:https://www.cnblogs.com/javasl/p/12334583.html
Copyright © 2011-2022 走看看