最近工做中常常使用Spring JDBC操做數據庫,也斷斷續續的看了一些源碼,便有了寫一些總結的想法,但願在能幫助別人的同時,也加深一下本身對Spring JDBC的理解。java
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使用很簡單,注入一個數據源就可使用了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}); }
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的使用基本上和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接口
@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(); } }); }
只須要注入數據源便可
<!-- 省略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