在经典的 JDBC 用法中, SQL 参数是用占位符 ? 表示,并且受到位置的限制. 定位参数的问题在于, 一旦参数的顺序发生变化, 就必须改变参数绑定.
在 Spring JDBC 框架中, 绑定 SQL 参数的另一种选择是使用具名参数(named parameter).
NamedParameterJdbcTemplate包装了底层的Spring-JDBCTemplate,使其占位符可以用具名参数表示。NamedParameterJdbcTemplate可以使用全部jdbcTemplate方法
什么是具名参数?
具名参数: SQL 按名称(以冒号开头)而不是按位置进行指定. 具名参数更易于维护, 也提升了可读性. 具名参数由框架类在运行时用占位符取代
具名参数只在 NamedParameterJdbcTemplate 中得到支持。
获取新增的主键:
NamedParameterJdbcTemplate还新增了KeyHolder类,使用它我们可以获得主键,类似Mybatis中的useGeneratedKeys。
使用NamedParameterJdbcTemplate修改之前的Spring-JDBCTemplate的实现CRUD:
/**
* NamedParameterJdbcTemplate对springJdbcTemplate进行了包装,使得sql语句能够不仅可以用占位符
* 还可以对占位符命名。
*/
public class Spring_NameParameterJdbcTemplate {
//使用数据源连接池来注册JDBC
private NamedParameterJdbcTemplate jdbc = new NamedParameterJdbcTemplate(JDBCUtils.getDatasource());
public static void main(String[] args) {
Spring_NameParameterJdbcTemplate nameParameterJdbcTemplate = new Spring_NameParameterJdbcTemplate();
User u = new User();
u.setMoney(880);
u.setName("王二小");
User user = nameParameterJdbcTemplate.findUser1(u);
System.out.println(user);
//返回多个会报错。。。
System.out.println(nameParameterJdbcTemplate.findUser("小肥仔"));
System.out.println(nameParameterJdbcTemplate.findUserById(3));
}
public void addUser(User user) {
final String sql = "insert into bank(name,money) values(:name,:money)";
SqlParameterSource parameterSource = new BeanPropertySqlParameterSource(user);//利用反射获取属性对应sql的参数
//获取刚插入的主键并返回
KeyHolder holder = new GeneratedKeyHolder();
jdbc.update(sql,parameterSource,holder);
int id = holder.getKey().intValue();
user.setId(id);
}
public void deleteUser(String userName) {//删除操作
String sql = "delete from bank where name=:n";
Map<String,String> params = new HashMap<>();//如果使用的是map来替换命名参数,则参数名可以不对应属性名。
params.put("n",userName);
jdbc.update(sql, params);
}
public void updateUser(User user) {//更新操作
String sql = "update bank set name=:name , money=:money where id=:id";
SqlParameterSource parameterSource = new BeanPropertySqlParameterSource(user);//利用反射获取属性对应sql的参数
jdbc.update(sql,parameterSource);
}
public User findUser(String userName) {//查询
String sql = "select * from bank where name=:n";
Map params = new HashMap();//如果使用的是map来替换命名参数,则参数名可以不对应属性名。
params.put("n",userName);
//这里只能返回一个数据,返回多个会报错。
return jdbc.queryForObject(sql,params,new BeanPropertyRowMapper<User>(User.class));
}
public User findUser1(User user) {//查询
String sql = "select * from bank where name=:name";
SqlParameterSource parameterSource = new BeanPropertySqlParameterSource(user);//利用反射获取属性对应sql的参数
//这里只能返回一个数据,返回多个会报错。
return jdbc.queryForObject(sql,parameterSource,new BeanPropertyRowMapper<User>(User.class));
}
public String findUserById(int id) {
String sql = "select name from bank where id=:id";
Map<String,Integer> params = new HashMap<>();
params.put("id",id);
return jdbc.queryForObject(sql, params, String.class);
}
public boolean toUser(User user, User toUser, float money) {
if (money > 0.0001) {
user.setMoney(user.getMoney() - money);
toUser.setMoney(toUser.getMoney() + money);
updateUser(user);
updateUser(toUser);
return true;
} else
throw new RuntimeException("转账金额不能为负数!");
}
}
queryForObject()方法里边的参数parameterSource,是利用反射来替换具名参数的。所以其命名必须与对应的类的属性名一致。
如果是Map参数,则不用与对应的类的属性名一致。
有一些方法只能返回一个数据,返回多个会报错。如:
//这里只能返回一个数据,返回多个会报错。
User user = jdbc.queryForObject(sql,parameterSource,User.class);
return user;
重要方法小结:
public Object queryForObject(String sql, Map paramMap, RowMapper rowMapper)
public Object queryForObject(String sql, SqlParameterSource paramSource, RowMapper rowMapper)
SqlParameterSource的两个主要实现
获取主键:
因为Spring 3.1支持JdbcTemplate和NamedParameterJdbcTemplate。JdbcTemplate和NamedParameterJdbcTemplate现在提供L了SimpleJdbcTemplate的所有功能。
也就是说SimpleJdbcTemplate则是对NamedParameterJdbcTemplate的包装。这是一个经典Spring JdbcTemplate的基于java 5(因为java5以上才支持泛型和自动装箱)的便利包装器,利用可变参数和自动装箱,并仅公开最常用的操作,以简化JdbcTemplate的使用。
如果需要调用不太常用的模板方法,请使用getJdbcOperations()方法(或直接的JdbcTemplate)。这包括指定SQL类型的任何方法、使用不太常用的回调函数(如RowCallbackHandler)的方法、使用PreparedStatementSetters而不是参数数组进行更新的方法、存储过程以及批处理操作。
注意:该类的实例在配置之后是线程安全的。
构造器有三个:
SimpleJdbcTemplate(DataSource dataSource)
SimpleJdbcTemplate并不能使用NamedParameterJdbcTemplate和JdbcTemplate的一些方法。但是我们可以利用某个方法将SimpleJdbcTemplate转化为NamedParameterJdbcTemplate或者JdbcTemplate,从而调用其方法。
如SimpleJdbcTemplate的update方法没有三个参数的。注释里面的是NamedParameterJdbcTemplate的方法。可以传入KeyHolder
所以我们要想实现该方法需要转化。
public void addUser(User user) {
final String sql = "insert into bank(name,money) values(:name,:money)";
SqlParameterSource parameterSource = new BeanPropertySqlParameterSource(user);//利用反射获取属性对应sql的参数
//获取刚插入的主键并返回
KeyHolder holder = new GeneratedKeyHolder();
jdbc.getNamedParameterJdbcOperations().update(sql,parameterSource,holder);
int id = holder.getKey().intValue();
user.setId(id);
}
利用SimpleJdbcTemplate改写上面的NamedParameterJdbcTemplate实现的CRUD操作。
/**
* 利用SimpleJdbcTemplate改写上面的NamedParameterJdbcTemplate实现的CRUD操作。
*/
public class Spring_SimpleJdbcTemplate {
//使用数据源连接池来注册JDBC
private SimpleJdbcTemplate jdbc = new SimpleJdbcTemplate(JDBCUtils.getDatasource());
public static void main(String[] args) {
Spring_SimpleJdbcTemplate simpleJdbcTemplate = new Spring_SimpleJdbcTemplate();
User u = new User();
u.setId(5);
u.setMoney(880);
u.setName("王三小");
//simpleJdbcTemplate.deleteUser(u.getName());
//System.out.println(u);
//返回多个会报错。。。
System.out.println(simpleJdbcTemplate.findUser1(u));
//simpleJdbcTemplate.updateUser(u);
System.out.println(simpleJdbcTemplate.findUserById(3));
}
public void addUser(User user) {
final String sql = "insert into bank(name,money) values(:name,:money)";
SqlParameterSource parameterSource = new BeanPropertySqlParameterSource(user);//利用反射获取属性对应sql的参数
//获取刚插入的主键并返回
KeyHolder holder = new GeneratedKeyHolder();
jdbc.getNamedParameterJdbcOperations().update(sql,parameterSource,holder);
int id = holder.getKey().intValue();
user.setId(id);
}
public void deleteUser(String userName) {//删除操作
//利用SimpleJdbcTemplate的话sql语句不能是具名参数。
String sql = "delete from bank where name=?";
/* Map params = new HashMap<>();//如果使用的是map来替换命名参数,则参数名可以不对应属性名。
params.put("n",userName);*/
jdbc.update(sql,userName);
}
public void updateUser(User user) {//更新操作
String sql = "update bank set name=? , money=? where id=?";
//可变参数
jdbc.update(sql,user.getName(),user.getMoney(),user.getId());
}
public User findUser(String userName) {//查询
String sql = "select * from bank where name=:n";
Map params = new HashMap();//如果使用的是map来替换命名参数,则参数名可以不对应属性名。
params.put("n",userName);
//这里只能返回一个数据,返回多个会报错。
return jdbc.getNamedParameterJdbcOperations().queryForObject(sql,
params,new BeanPropertyRowMapper<User>(User.class));
}
public User findUser1(User user) {//查询
String sql = "select * from bank where name=? and money=?";
//这里只能返回一个数据,返回多个会报错。
return jdbc.queryForObject(sql,new BeanPropertyRowMapper<User>(User.class),user.getName(),user.getMoney());
}
public String findUserById(int id) {
String sql = "select name from bank where id=:id";
Map<String,Integer> params = new HashMap<>();
params.put("id",id);
User user= jdbc.queryForObject(sql,new ParameterizedBeanPropertyRowMapper<User>().newInstance(User.class),
id);//id利用了可变参数。
return user.getName();
}
public boolean toUser(User user, User toUser, float money) {
if (money > 0.0001) {
user.setMoney(user.getMoney() - money);
toUser.setMoney(toUser.getMoney() + money);
updateUser(user);
updateUser(toUser);
return true;
} else
throw new RuntimeException("转账金额不能为负数!");
}
}
2、使用可变参数的时候,要注意其位置,以及某个位置的参数的意义。如图:第一个是执行的sql语句。第二个是利用反射得到的类的属性信息RowMapper类型。第三个则是可变参数,用于设置占位符的值。
3、对比转化为了NamedParameterJdbcTemplate来操作的queryForObject方法。第二个位置用来设置sql中具名参数的值
4、new ParameterizedBeanPropertyRowMapper().newInstance(User.class),它必须使用newInstance()才能为指定的类构造新实例映射目标类。而new BeanPropertyRowMapper(User.class)。则可以指定映射的目标类。
①BeanPropertyRowMapper——将行转换为指定映射目标类的新实例的RowMapper实现。映射的目标类必须是顶级类,并且必须具有默认的或无参数的构造函数。
根据从结果集元数据获得的列名匹配,将列值映射为对应属性的公共setter。名称可以直接匹配,也可以通过使用驼峰大小写将分隔下划线的部分的名称转换为相同的名称来匹配。
在目标类中为许多常见类型的字段提供了映射,例如:String, boolean, boolean,其构造器有三个
/**
* 使用封装好的JDBC来改写DAO实现。
*/
public class DAOImplSpringJdbc implements Dao {
//使用数据源连接池来注册JDBC
private NamedParameterJdbcTemplate jdbc = new NamedParameterJdbcTemplate(JDBCUtils.getDatasource());
public void addUser(User user) {
final String sql = "insert into bank(name,money) values(:name,:money)";
SqlParameterSource parameterSource = new BeanPropertySqlParameterSource(user);//利用反射获取属性对应sql的参数
//获取刚插入的主键并返回
KeyHolder holder = new GeneratedKeyHolder();
jdbc.update(sql,parameterSource,holder);
int id = holder.getKey().intValue();
user.setId(id);
}
public void deleteUser(String userName) {//删除操作
String sql = "delete from bank where name=:n";
Map<String,String> params = new HashMap<>();//如果使用的是map来替换命名参数,则参数名可以不对应属性名。
params.put("n",userName);
jdbc.update(sql, params);
}
public void updateUser(User user) {//更新操作
String sql = "update bank set name=:name , money=:money where id=:id";
SqlParameterSource parameterSource = new BeanPropertySqlParameterSource(user);//利用反射获取属性对应sql的参数
jdbc.update(sql,parameterSource);
}
public User findUser(String userName) {//查询
String sql = "select * from bank where name=:n";
Map params = new HashMap();//如果使用的是map来替换命名参数,则参数名可以不对应属性名。
params.put("n",userName);
//这里只能返回一个数据,返回多个会报错。
return jdbc.queryForObject(sql,params,new BeanPropertyRowMapper<User>(User.class));
}
public boolean toUser(User user, User toUser, float money) {
if (money > 0.0001) {
user.setMoney(user.getMoney() - money);
toUser.setMoney(toUser.getMoney() + money);
updateUser(user);
updateUser(toUser);
return true;
} else
throw new RuntimeException("转账金额不能为负数!");
}
}
之前的工厂类:
/**
* 创建工厂类,用于生产DAO接口实例
*/
public class DaoFactory {
private static Dao daoImpl = null;
private DaoFactory()
static{
try {
InputStream inputs = new FileInputStream(
new File("src/main/java/main/java/DAO_design/Factory/DAOImpl.properties"));
Properties properties = new Properties();
properties.load(inputs);
String DaoImplClass = properties.getProperty("DaoImplClass");
//创建实例
//daoImpl = Class.forName(DaoImplClass).newInstance();//过时方法
//替代方案
Class<?> clazz = Class.forName(DaoImplClass);
daoImpl = (Dao) clazz.getDeclaredConstructor().newInstance();
} catch (IllegalAccessException | InstantiationException |
InvocationTargetException | ClassNotFoundException | NoSuchMethodException | IOException e) {
e.printStackTrace();
}
}
public static Dao getInstance(){
return daoImpl;
}
}
我们在配置文件上添加上DAOImplSpringJdbc的位置。则可以运行了。