Spring JDBC(一)jdbcTemplate

前言

最近工做中常常使用Spring JDBC操做數據庫,也斷斷續續的看了一些源碼,便有了寫一些總結的想法,但願在能幫助別人的同時,也加深一下本身對Spring JDBC的理解。java

Spring JDBC 簡介

Spring JDBC 是spring 官方提供的一個持久層框架,對jdbc進行了抽象和封裝,消除了重複冗餘的jdbc重複性的代碼,使操做數據庫變的更簡單。mysql

但Spring JDBC自己並非一個orm框架,與hibernate相比,它須要本身操做sql,手動映射字段關係,在保持靈活性的同時,勢必形成了開發效率的下降。若是須要使用完整的orm框架操做數據庫,可使用hibernate或者spring Data Jpa。git

Spring JDBC不一樣版本的api會稍有變更,但整體的變化不大,如下測試代碼均使用4.3.11.RELEASE。github

模板類

Spring JDBC 提供了模板類對數據庫簡化對數據庫的操做,其中JdbcTemplate是最經常使用的,若是須要使用命名參數可使用NamedParameterJdbcTemplate,SimpleJdbcTemplate在3.1版本已經標記過期,在我使用的4.3版本中,已經被刪除。spring

JdbcTemplate 入門示例

JdbcTemplate使用很簡單,注入一個數據源就可使用了sql

public class A001SpringJdbcJdbcTemplateTest {
  private JdbcTemplate jdbcTemplate;

  @Before
  public void init() {
    DriverManagerDataSource dataSource = new DriverManagerDataSource();
    dataSource.setDriverClassName("com.mysql.jdbc.Driver");
    dataSource.setUrl("jdbc:mysql://localhost:3306/blogsrc?useUnicode=true&characterEncoding=UTF-8");
    dataSource.setUsername("root");
    dataSource.setPassword("zhao");
    jdbcTemplate = new JdbcTemplate(dataSource);
  }

  @Test
  public void queryTest() {
    String sql = "select * from user";
    List<Map<String, Object>> users = jdbcTemplate.queryForList(sql);
    System.out.println(users);
  }

}

對於參數賦值,能夠採用佔位符的方式數據庫

@Test
  public void queryByParameterTest() {
    String sql = "select * from user where id =?";
    List<Map<String, Object>> users = jdbcTemplate.queryForList(sql, 1L);
    List<Map<String, Object>> users1 = jdbcTemplate.queryForList(sql, new Object[] {1L});
  }

mapper映射

Spring JDBC 經過mapper接口把resultSet對象中的數據映射爲java對象,例如上述例子中返回 List<Map<String, Object>>,其實使用的是ColumnMapRowMapper的mapper實現。咱們本身能夠經過實現RowMapper接口的方式自定義從resultSet到java對象的映射關係。api

先建立一個tablespringboot

CREATE TABLE `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `password` varchar(255) NOT NULL,
  `user_name` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

User實體類app

public class User implements Serializable {
  private Long id;
  private String userName;
  private String password;
  // 省略 getter setter
}

自定義mapper映射

@Test
  public void simpleMapperTest() {
    String sql = "select * from user";
    List<User> users = jdbcTemplate.query(sql, new RowMapper<User>() {
      public User mapRow(ResultSet rs, int rowNum) throws SQLException {
        User user = new User();
        user.setId(rs.getObject("id") == null ? null : rs.getLong("id"));
        user.setUserName(rs.getString("user_name"));
        user.setPassword(rs.getString("password"));
        return user;
      }
    });
  }

調用存儲過程

對於數據庫數據的更新操做,能夠直接調用update接口,若是是存儲過程,能夠經過execute完成調用。

新建一個簡單的存儲過程test

DROP PROCEDURE IF EXISTS test;
CREATE PROCEDURE test (IN userId BIGINT ( 20 ),OUT userName VARCHAR ( 200 ) ) 
BEGIN
SET userName = ( SELECT user_name FROM USER WHERE id = userId );
END;
@Test
  public void proTest() {
    String sql = "insert into user (user_name,password) VALUES (?, ?)";
    jdbcTemplate.update(sql, "趙孤鴻", "123456"); // 插入數據
    String userName = jdbcTemplate.execute(new CallableStatementCreator() {
      public CallableStatement createCallableStatement(Connection con) throws SQLException {
        String proc = "{call test(?,?)}";
        CallableStatement cs = con.prepareCall(proc);
        cs.setLong(1, 1L);// 設置輸入參數的值 索引從1開始
        cs.registerOutParameter(2, Types.VARCHAR);// 設置輸出參數的類型
        return cs;
      }
    }, new CallableStatementCallback<String>() {
      public String doInCallableStatement(CallableStatement cs) throws SQLException, DataAccessException {
        cs.execute();
        return cs.getString(2);// 返回輸出參數
      }
    });
    System.out.println(userName);
  }

NamedParameterJdbcTemplate

NamedParameterJdbcTemplate的使用基本上和JdbcTemplate相似,只不過參數的賦值方式由佔位符變成了命名參數,命名參數優點在於,若是一個相同的參數出現了屢次,只須要進行一次賦值便可。
建立NamedParameterJdbcTemplate對象的兩種方式

// 方式1
    namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate);
    // 方式2
    namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
@Test
  public void namedParameterJdbcTemplateTest() {
    String sql = "select * from user where id =:id";
    Map<String, Object> parameters = new HashMap<String, Object>();
    parameters.put("id", 1L);
    List<Map<String, Object>> users = namedParameterJdbcTemplate.queryForList(sql, parameters);
    System.out.println(users);
  }

batchUpdate

對於大數據量的數據更新,能夠採用batchUpdate接口

@Test
  public void batchUpdateTest() {
    String sql = "insert into user (user_name,password) VALUES (?, ?)";
    List<User> users = Lists.newArrayList();
    for (int i = 0; i <= 10; i++) {
      User user = new User();
      user.setUserName("xiaoming");
      user.setPassword("123456");
      users.add(user);
    }
    jdbcTemplate.batchUpdate(sql, new BatchPreparedStatementSetter() {
      @Override
      public void setValues(PreparedStatement ps, int i) throws SQLException {
        User user = users.get(i);
        int count = 0;
        ps.setString(++count, user.getUserName());// 索引從1開始
        ps.setString(++count, user.getPassword());
      }

      @Override
      public int getBatchSize() {
        return users.size();
      }
    });
  }

與spring整合

只須要注入數據源便可

<!-- 省略dataSource相關配置 -->
    
    <!-- 配置 Spirng 的 JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 配置 NamedParameterJdbcTemplate -->
    <bean id="namedParameterJdbcTemplate"
        class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
        <constructor-arg ref="dataSource"></constructor-arg>
    </bean>

若是是springboot項目,直接使用便可

@Autowired
  private JdbcTemplate jdbcTemplate;

注意事項

關於queryForObject,若是沒有查詢到,或者查詢到多個,都會拋異常,看一下源碼

// 未查詢到,或者查詢到多個,都會拋異常
  public static <T> T requiredSingleResult(Collection<T> results) throws IncorrectResultSizeDataAccessException {
    int size = (results != null ? results.size() : 0);
    if (size == 0) {
      throw new EmptyResultDataAccessException(1);
    }
    if (results.size() > 1) {
      throw new IncorrectResultSizeDataAccessException(1, size);
    }
    return results.iterator().next();
  }

queryForMap最終走的也是queryForObject方法,所以,使用時也要注意

在自定義mapper獲取ResultSet的值時,獲取基本類型數據會有默認值,解決辦法以下

Long id =  rs.getLong("id");//若是id爲null,會默認0
  Long id1 =  rs.getObject("id") == null ? null : rs.getLong("id");//先判斷是否爲null

完整的 源碼 https://github.com/zhaoguhong/blogsrc

相關文章
相關標籤/搜索