4.1使用普通的JDBC所带来的问题
4.2 引入Spring 的JDBC支持
三种数据库访问操作:
1、Template Method模式的实用程序类(JdbcTemplate,NamedParameterJdbcTemplate),删除程序中的代码块,处理资源清理,完成JDBC操作。
2、使用SimpleJdbcInsert和SimpleJdbcCall之类的类以及数据库元数据,简化查询。
3、通过使用MappingSqlQuery ,SqlUpdate,Storedprocedure类,将数据库操作表示为可以重复使用的java对象。
4.2.1 管理JDBC连接
1、DriverManager
2、DataSource,这种更好是一个广义的连接工厂,它能让我们隐藏数据库连接参数、连接池以及来自应用程序的事物管理问题。
(1)、创建项目
(2) 、pom.xml文件添加
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.0.5.RELEASE</version>
</dependency>
(3)创建所需要的数据库
(4)定义一个dataSource Bean
@Configuration
public class Ch4Configuration {
@Bean
public DataSource dataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/fscdb");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}
}
同样也可以使用SingleConnectionDataSource 创建dataSource对象
(7)Main
AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext(Ch4Configuration.class);
DataSource dataSource=applicationContext.getBean("dataSource",DataSource.class);
Connection connection =dataSource.getConnection();
System.out.println(connection.isClosed());
connection.close();
System.out.println(connection.isClosed());
(8)所需的jar包
4.2.2 配置和使用Spring的JDBC支持
(1)创建对象以及接口
public class Account {
private long id;
private String ownername;
private double balance;
private Date accessTime;
private boolean locked;
//geter、seter省略
}
public interface AccountDao {
public void insert(Account account);
public void update(Account account);
public void update(List<Account> accounts);
public void delete(long accountId);
public Account find(long accountId);
public List<Account> find(List<Long> accountIds);
public Account find(String ownerName);
public List<Account> find(boolean locked);
}
(2)实现接口:public class AccountDaoJdbcImpl implements AccountDao
public class AccountDaoJdbcImpl implements AccountDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
namedParameterJdbcTemplate=new NamedParameterJdbcTemplate(jdbcTemplate);
}
//method implementations
}
(3)使用dataSource Bean 完成jdbcTemplat Bean 依赖
(4)通过使用AccountDaoJdbcImpl类创建accountDao Bean ,并将jdbcTemplate Bean注入其中
@Configuration
public class Ch4Configuration {
@Bean
public DataSource dataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("123456");
return dataSource;
}
@Bean
public JdbcTemplate jdbcTemplate(){
JdbcTemplate jdbcTemplate=new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource());
return jdbcTemplate;
}
@Bean
public AccountDao accountDao() throws SQLException{
AccountDaoJdbcImpl accountDao = new AccountDaoJdbcImpl();
accountDao.setJdbcTemplate(jdbcTemplate());
return accountDao;
}
}
(5)在main方法中对accountBean的查找
AnnotationConfigApplicationContext applicationContext=
new AnnotationConfigApplicationContext(Ch4Configuration.class);
AccountDao accountDao=applicationContext.getBean(AccountDao.class);
4.3使用Spring执行数据库的访问操作
使用jdbcTemplate更为特殊NameParameterjdbcTemplate来解释各种数据访问操作是如何被执行的。还有SimpleJdbcCall简化使用数据库元数据的查询,使用MappingSqlQuery、SqlUpdate、StoredProcedure类说明如何将SQL操作模块化为一个java对象并反复使用。
4.3.1执行查询
JdbcTemplat通过不同重载版本提供了各种方法执行查询,通常使用qurey()、qureForObject()、queryForList(..)、queryForMap(..)、queryForObject(..)、(接收不同输入参数、比如查询字符串、查询输入参数值、类型、结果对象类型等)
find方法
//JdbcTemplate.queryForObject().该方法接收一个SQL查询、一个RowMapper对象以及一个可变参数对象作为查询输入参数。
return jdbcTemplate.queryForObject(
"select id,owner_name,balance,access_time,locked from account where id =?",
new RowMapper<Account>(){
//RowMapper接口创建一个匿名类。其mapRow(..)方法返回一个Account对象
public Account mapRow(ResultSet rs, int rowNum)
throws SQLException {
Account account = new Account();
account.setId(rs.getLong("id"));
account.setOwnername(rs.getString("owner_name"));
account.setBalance(rs.getDouble("balance"));
account.setAccessTime(rs.getTimestamp("access_time"));
account.setLocked(rs.getBoolean("locked"));
return account;
};
}
, accountId);
(4)插入数据 INSERT INTO account(id,owner_name,balance,access_time,locked) VALUES(100,'john doe',10.0,'2014-01-01',FALSE);
(5)在主方法中获取记录
AnnotationConfigApplicationContext applicationContext=new AnnotationConfigApplicationContext(Ch4Configuration.class);
AccountDao accountDao=applicationContext.getBean(AccountDao.class);
Account account =accountDao.find(100L);
System.out.println(account.getId());
System.out.println(account.getOwnername());
System.out.println(account.getBalance());
System.out.println(account.getAccessTime());
System.out.println(account.isLocked());
4.3.2使用命名参数进行查询
NamedParameterJdbcTemplate实际上封装了JdbcTemplate,所以几乎所有的艰苦工作都是由JdbcTemplate来完成
(1)添加NamedParameterJdbcTemplate属性
public class AccountDaoJdbcImpl implements AccountDao {
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
namedParameterJdbcTemplate=new NamedParameterJdbcTemplate(jdbcTemplate);
}
}
(2)使用NamedParameterJdbcTemplate实现find而不是JdbcTemplate。
@Override
public Account find(String ownerName) {
String sql="select id,owner_name,balance,access_time,locked from account where owner_name=:ownername";
//JdbcTemplate.queryForObject().该方法接收一个SQL查询、一个RowMapper对象以及一个可变参数对象作为查询输入参数。
return namedParameterJdbcTemplate.
queryForObject
(sql, Collections.singletonMap("ownername", ownerName), new RowMapper<Account>() {
//RowMapper接口创建一个匿名类。其mapRow(..)方法返回一个Account对象
@Override
public Account mapRow(ResultSet rs, int rowNum)
throws SQLException {
Account account = new Account();
account.setId(rs.getLong("id"));
account.setOwnername(rs.getString("owner_name"));
account.setBalance(rs.getDouble("balance"));
account.setAccessTime(rs.getTimestamp("access_time"));
account.setLocked(rs.getBoolean("locked"));
return account;
}
});
}
4.3.3 使用in子句编写查询
4.3.4 在JdbcTemplate中使用PrepareStatements
相对于每次动态的创建查询字符串,使用PrepareStatements的另一个好处是免糟sql注入攻击
(1)在JdbcTemplate.query()方法使用PrepareStatementsCreator实例来获取PrepareStatement对象。通过使用PrepareStatementsCreatorFactory类,可以使用不同的参数创建多个PrepareStatement对象。
@Override
public List<Account> find(boolean locked) {
String sql="select * from account where locked=?";
PreparedStatementCreatorFactory preparedStatementCreatorFactory=new PreparedStatementCreatorFactory(sql, new int[]{Types.BOOLEAN});
return jdbcTemplate.query(preparedStatementCreatorFactory.newPreparedStatementCreator(new Object[]{locked}),
new RowMapper<Account>() {
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account account = new Account();
account.setId(rs.getLong("id"));
account.setOwnername(rs.getString("owner_name"));
account.setBalance(rs.getDouble("balance"));
account.setAccessTime(rs.getTimestamp("access_time"));
account.setLocked(rs.getBoolean("locked"));
return account;
};
}
);
}
为了可以作为回调的接口使用,在JdbcTemplate.query()方法还接收创建了自PrepareStatementSetter接口的实例。该实例用来在PrepareStatement对象上设置参数值。JdbcTemplate创建PrepareStatement,而该回调仅负责设置参数值。
@Override
public List<Account> find(boolean locked) {
String sql="select * from account where locked=?";
PreparedStatementCreatorFactory preparedStatementCreatorFactory=new PreparedStatementCreatorFactory(sql, new int[]{Types.BOOLEAN});
return jdbcTemplate.query(preparedStatementCreatorFactory.newPreparedStatementCreator(new Object[]{locked}),
new RowMapper<Account>() {
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account account = new Account();
account.setId(rs.getLong("id"));
account.setOwnername(rs.getString("owner_name"));
account.setBalance(rs.getDouble("balance"));
account.setAccessTime(rs.getTimestamp("access_time"));
account.setLocked(rs.getBoolean("locked"));
return account;
};
}
);
}
4.3.5 插入、更新、删除
(1)插入
@Override
public void insert(Account account) {
String sql="insert into account (owner_name,balance,access_time,locked)values (?,?,?,?)";
PreparedStatementCreatorFactory psCreatorFactory=new PreparedStatementCreatorFactory(sql,
new int[]{
Types.VARCHAR,Types.DOUBLE,Types.TIMESTAMP,Types.BOOLEAN
}
);
KeyHolder keyHandler=new GeneratedKeyHolder();
int count = jdbcTemplate.update(psCreatorFactory.newPreparedStatementCreator(new Object[]{
account.getOwnername(),account.getBalance(),account.getAccessTime(),account.isLocked()
}),keyHandler);
if(count!=1)throw new InsertFailedException("count insert account ");
account.setId(keyHandler.getKey().longValue());
}
(2)更新
@Override
public void update(Account account) {
String sql="UPDATE account SET owner_name=?,balance=?,access_time=?,locked=? WHERE id=?";
int count=jdbcTemplate.update(sql,
account.getOwnername(),account.getBalance(),account.getAccessTime(),account.isLocked(),account.getId()
);
if(count!=1)throw new InsertFailedException("Cannot update account ");
}
(3)删除
@Override
public void delete(long accountId) {
int count=jdbcTemplate.update("delete from account where id=?",accountId) ;
if(count!=1)throw new InsertFailedException("Cannot delete account ");
}
4.3.6调用存储过程和存储函数(p112)
4.3.7执行批处理操作
可以将多个更新操作组合在一起,并且在一个由JdbcTemplate创建的PrepareStatement对象中执行这些操作,该过程被称为批处理。批处理操作可以减少往返数据库的次数,有助于提高数据库的访问性能。为此提供了一个batchUpdate(..)方法。
4.3.8 处理BLOB和CLOB对象(p124)
4.3.9 访问特定供应商的JDBC方法(p125)
4.3.10 执行ddl操作