跳至主要內容

SpringJDBC

soulballad微服务SpringCloud NetfilxSpringCloud约 1458 字大约 5 分钟

数据源

数据源是数据库连接的来源,通过 DataSource 接口获取

类型

  • 通用数据源
    • javax.sql.DataSource
  • 分布式数据源
    • javax.sql.XADatasource
  • 嵌入式数据源
    • org.springframework.jdbc.datasource.embedded.EmbeddedDatabase

SpringBoot 实际使用场景

在 Spring Boot 2.0+,如果应用采用 Spring Web MVC 作为 Web 服务, 默认情况下,使用 嵌入式 Tomcat。

如果采用Spring Web Flux,默认情况下,使用 Netty Web Server(嵌入式)

从 Spring Boot 1.4 支持 FailureAnalysisReporter 实现

WebFlux

Mono : 0 - 1 Publisher(类似于Java 8 中的 Optional)

Flux: 0 - N Publisher(类似于Java 中的 List)

传统的 Servlet 采用 HttpServletRequest、HttpServletResponse

WebFlux 采用:ServerRequest、ServerResponse(不再限制于 Servlet 容器,可以选择自定义实现,比如 Netty Web Server)

单数据源的场景

数据连接池技术
Apache Commons DBCPopen in new window
  • commons-dbcp2
    • 依赖:commons-pool2
  • commons-dbcp(老版本)
    • 依赖:commons-pool
Tomcat DBCPopen in new window
WebMVC 和 WebFlux
  1. 项目结构

    1570012000947

  2. 配置数据源

    spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    spring.datasource.url=jdbc:mysql://172.16.11.125:3306/test
    spring.datasource.username=root
    spring.datasource.password=123456
    
  3. 定义实体类 User

    public class User {
    
        private Long id;
        private String name;
    
        // get&set
    
        @Override
        public String toString() {
            return "User{id=" + id + ", name='" + name + '\'' + '}';
        }
    }
    
  4. 定义 UserRepository

    /**
     * 用户仓储(SQL、或NOSQL、或内存型)
     */
    @Repository
    public class UserRepository {
    
        private final DataSource dataSource;
    
        // 推荐使用构造注入
        public UserRepository(DataSource dataSource) {
            this.dataSource = dataSource;
        }
    
        public boolean save(User user) {
            System.err.println("save user: " + user);
            System.err.println("");
            return true;
        }
    
        public Collection<User> findAll() {
            return Collections.emptyList();
        }
    }
    
  5. WebMVC 写法

    @RestController
    public class UserController {
    
        private final ExecutorService executorService = Executors.newFixedThreadPool(5);
        private final UserRepository userRepository;
    
        public UserController(UserRepository userRepository) {
            this.userRepository = userRepository;
        }
    
        @RequestMapping("/web/mvc/user/save")
        public Boolean save(@RequestBody User user) throws ExecutionException, InterruptedException {
    //        Future<Boolean> future
    //                = executorService.submit(() -> {userRepository.save(user);});
    //        return future.get();
            return userRepository.save(user);
        }
    }
    
  6. WebFlux 写法

    • UserHandler

      @Component
      public class UserHandler {
      
          private final UserRepository userRepository;
      
          public UserHandler(UserRepository userRepository) {
              this.userRepository = userRepository;
          }
      
          public Mono<ServerResponse> save(ServerRequest serverRequest) {
              // 在 Spring Web MVC 中使用 @RequestBody
              // 在 Spring Web Flux 中使用 ServerRequest
              // Mono<User> 类似于 Optional<User>
              Mono<User> userMono = serverRequest.bodyToMono(User.class);
              // map 相当于转化工作
              Mono<Boolean> booleanMono = userMono.map(userRepository::save);
              return ServerResponse.ok().body(booleanMono, Boolean.class);
          }
      }
      
    • WebFluxConfiguration

      @Configuration
      public class WebFluxConfiguration {
      
          @Bean
          public RouterFunction<ServerResponse> saveUser(UserHandler userHandler) {
              // route: 静态导入
              return route(POST("/web/flux/user/save"), userHandler::save);
          }
      }
      
  7. postman 测试

    • webmvc:/web/mvc/user/save

      1570011837900

    • webflux:/web/flux/user/save

      1570011873947

多数据源场景

配置多个数据源

  1. 新增数据源配置类,MultiDataSourceConfiguration

    @Configuration
    public class MultiDataSourceConfiguration {
    
        @Bean
        @Primary
        public DataSource masterDataSource() {
            DataSourceBuilder<?> dataSourceBuilder = DataSourceBuilder.create();
            DataSource dataSource = dataSourceBuilder
                .driverClassName("com.mysql.jdbc.Driver")
                .url("jdbc:mysql://172.16.11.125:3306/test")
                .username("root")
                .password("123456").build();
            return dataSource;
        }
    
        @Bean
        public DataSource slaveDataSource() {
            DataSourceBuilder<?> dataSourceBuilder = DataSourceBuilder.create();
            DataSource dataSource = dataSourceBuilder
                .driverClassName("com.mysql.jdbc.Driver")
                .url("jdbc:mysql://172.16.11.125:3306/test2")
                .username("root")
                .password("123456").build();
            return dataSource;
        }
    }
    
    
  2. 根据 Bean 注入不同数据源

    注入的时候通过 @Qualifier 来区分不同数据源

    @Repository
    public class UserRepository {
    
        private final DataSource dataSource;
        private final DataSource masterDataSource;
        private final DataSource slaveDataSource;
    
        public UserRepository(DataSource dataSource,
                              @Qualifier("masterDataSource") DataSource masterDataSource,
                              @Qualifier("slaveDataSource") DataSource slaveDataSource) {
            this.dataSource = dataSource;
            this.masterDataSource = masterDataSource;
            this.slaveDataSource = slaveDataSource;
        }
    
    
        public boolean save(User user) {
            System.err.println("save user: " + user);
            return true;
        }
    
        public Collection<User> findAll() {
            return Collections.emptyList();
        }
    }
    

事务

事务用于提供数据完整性,并在并发访问下确保数据视图的一致性。

重要概念

  • 自动提交模式(Auto-commit mode)

    • jdbc 自动提交

      public boolean save(User user) {
      
          Connection connection = null;
          boolean success = false;
      
          try {
              connection = dataSource.getConnection();
              PreparedStatement prepareStatement = 
                  connection.prepareStatement("INSERT INTO user(name) values (?);");
              prepareStatement.setString(1, user.getName());
              success = prepareStatement.executeUpdate() > 0;
              prepareStatement.close();
          } catch (SQLException e) {
              e.printStackTrace();
          } finally {
              if (null != connection) {
                  try {
                      connection.close();
                  } catch (SQLException e) {
                      e.printStackTrace();
                  }
              }
          }
      
          return success;
      }
      
    • jdbc 手动提交

      public boolean save(User user) {
      
          Connection connection = null;
          boolean success = false;
      
          try {
              connection = dataSource.getConnection();
              connection.setAutoCommit(false);
              PreparedStatement prepareStatement = 
                  connection.prepareStatement("INSERT INTO user(name) values (?);");
              prepareStatement.setString(1, user.getName());
              success = prepareStatement.executeUpdate() > 0;
              prepareStatement.close();
          } catch (SQLException e) {
              e.printStackTrace();
          } finally {
              if (null != connection) {
                  try {
                      connection.commit();
                      connection.close();
                  } catch (SQLException e) {
                      e.printStackTrace();
                  }
              }
          }
      
          return success;
      }
      
  • 事务隔离级别(Transaction isolation levels)

    • Java JDBC 隔离级别,javax.sql.Connection

      1570015760275

      • TRANSACTION_READ_UNCOMMITTED
      • TRANSACTION_READ_COMMITTED
      • TRANSACTION_REPEATABLE_READ
      • TRANSACTION_SERIALIZABLE

      从上至下,级别越高,性能越差

    • Spring JDBC 隔离

      Spring Transaction 实现重用了 JDBC API

      Isolation -> TransactionDefinition

      1570015831507

      1570015881542

      • ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED
      • ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED
      • ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ
      • ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE
  • 保护点(Savepoints)

    public boolean savePoint(User user) {
    
        boolean success = false;
        Connection connection = null;
        try {
            connection = dataSource.getConnection();
            connection.setAutoCommit(false);
            // 创建还原点
            Savepoint savepoint = connection.setSavepoint("T1");
    
            try {
                // 其他事务操作
                transactionSave(user);
            } catch (Exception e) {
                // 发生异常之后,回滚到还原点
                connection.rollback(savepoint);
            }
    
            PreparedStatement prepareStatement = connection.prepareStatement("INSERT INTO user(name) values (?);");
            prepareStatement.setString(1, user.getName());
            success = prepareStatement.executeUpdate() > 0;
            prepareStatement.close();
    
            connection.commit();
            connection.releaseSavepoint(savepoint);
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (null != connection) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return success;
    }
    

Spring 事务控制

@Annotation 驱动

private final JdbcTemplate jdbcTemplate;

public UserRepository(DataSource dataSource,
                      @Qualifier("masterDataSource") DataSource masterDataSource,
                      @Qualifier("slaveDataSource") DataSource slaveDataSource,
                      JdbcTemplate jdbcTemplate) {
    this.dataSource = dataSource;
    this.masterDataSource = masterDataSource;
    this.slaveDataSource = slaveDataSource;
    this.jdbcTemplate = jdbcTemplate;
}

@Transactional
public boolean save(User user) {
    System.err.println("save user: " + user);

    return jdbcTemplate.execute("INSERT INTO user(name) values (?);",
            (PreparedStatementCallback<Boolean>) preparedStatement -> {
                preparedStatement.setString(1, user.getName());
                return preparedStatement.executeUpdate() > 0;
            });
}

@Transaction

  • 通过动态代理来控制事务 - TransactionInterceptor

    1570016252654

  • 可以控制 rollback 的异常粒度:rollbackFor() 以及 noRollbackFor()

    1570016399212

  • 可以执行 事务管理器:transactionManager()

    1570016436116

API 驱动

private final PlatformTransactionManager platformTransactionManager;

public UserRepository(DataSource dataSource,
                      @Qualifier("masterDataSource") DataSource masterDataSource,
                      @Qualifier("slaveDataSource") DataSource slaveDataSource,
                      JdbcTemplate jdbcTemplate, 
                      PlatformTransactionManager platformTransactionManager) {
    this.dataSource = dataSource;
    this.masterDataSource = masterDataSource;
    this.slaveDataSource = slaveDataSource;
    this.jdbcTemplate = jdbcTemplate;
    this.platformTransactionManager = platformTransactionManager;
}

public boolean save(User user) {
    Boolean success = false;
    DefaultTransactionDefinition transactionDefinition = 
        new DefaultTransactionDefinition();
    TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);

    try {
        success = jdbcTemplate.execute("INSERT INTO user(name) values (?);",
                (PreparedStatementCallback<Boolean>) preparedStatement -> {
                    preparedStatement.setString(1, user.getName());
                    return preparedStatement.executeUpdate() > 0;
                });
        platformTransactionManager.commit(transaction);
    } catch (DataAccessException e) {
        platformTransactionManager.rollback(transaction);
        e.printStackTrace();
    }

    return success;
}

事务传播级别

在外层方法开启了事务,如果配置了传播级别,里面的方法也会拥有事务,这个事务类型和传播级别有关。

@Transactional
save(){
	// insert
	save1(); //如果save1没有配置Transactional,那么 save1 的事务跟传播级别有关
    		 //如果配置了事务传播,那么 save1 也会有事务
}
  • Propagation

    1570017846123

  • TransactionDefinition

    1570017925373

  1. 用reactive web,原来mvc的好多东西都不能用了?

    答:不是, Reactive Web 还是能够兼容 Spring WebMVC

  2. 开个线程池事务控制用API方式?比如开始写的Excutor.fixExcutor(5)

    答:TransactionSynchronizationManager 使用大量的ThreadLocal 来实现的

  3. 假设一个service方法给了@Transaction标签,在这个方法中还有其他service 的某个方法,这个方法没有加@Transaction,那么如果内部方法报错,会回滚吗?

    答:会的,当然可以过滤掉一些不关紧要的异常noRollbackFor()

  4. spring 分布式事务 生产环境实现方式有哪些?

    答:https://docs.spring.io/spring-boot/docs/2.0.0.M5/reference/htmlsingle/#boot-features-jta

    1570018563919

上次编辑于:
贡献者: soulballad