传智播客李勇老师的JDBC系列学习终于接近尾声了,好开心,能学到这么多的东西,还不赶快记录下来,留待以后回味!
如何使用开源项目DBCP(实际项目中常用)
主要分为三个步骤:
- 使用DBCP必须用的jar包。
- 添加dbcp的配置文件。
- Java API:BasicDataSourceFactory.createDataSource(prop);
不然会报异常:
java.lang.ClassNotFoundException: org.apache.commons.logging.LogFactory
初步原因是因为少导入了commons-logging.jar,导入此jar问题则会解决!
dbcp的配置文件(dbcpconfig.properties)如下:
#连接设置 driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/jdbc username=root password=yezi #<!-- 初始化连接 --> initialSize=10 #最大连接数量 maxActive=50 #<!-- 最大空闲连接 --> maxIdle=20 #<!-- 最小空闲连接 --> minIdle=5 #<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 --> maxWait=60000 #JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;] #注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。 connectionProperties=useUnicode=true;characterEncoding=gbk #指定由连接池所创建的连接的自动提交(auto-commit)状态。 defaultAutoCommit=true #driver default 指定由连接池所创建的连接的只读(read-only)状态。 #如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix) defaultReadOnly= #driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。 #可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE defaultTransactionIsolation=READ_UNCOMMITTED
代码如下:
public final class JdbcUtils { private static String url = "jdbc:mysql://localhost:3306/jdbc?generateSimpleParameterMetadata=true"; private static String user = "root"; private static String password = "yezi"; //private static DataSource myDataSource = new MyDataSource2(); private static DataSource myDataSource = null; private JdbcUtils() { } /* * 静态代码块 * 随着类的加载而执行,只执行一次,并优先于主函数。用于给类进行初始化。 */ static { try { Class.forName("com.mysql.jdbc.Driver"); //myDataSource = new MyDataSource2(); Properties prop = new Properties(); /* * 写死在程序里 */ //prop.setProperty("driverClassName", "com.mysql.jdbc.Driver"); //prop.setProperty("user", "root"); InputStream is = JdbcUtils.class.getClassLoader(). getResourceAsStream("dbcpconfig.properties"); prop.load(is); myDataSource = BasicDataSourceFactory.createDataSource(prop); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } //返回数据源,因为我们的程序只和DataSource打交道,不会直接访问连接池 public static DataSource getDataSource() { return myDataSource; } public static Connection getConnection() throws SQLException { //return DriverManager.getConnection(url, user, password); return myDataSource.getConnection(); } public static void free(ResultSet rs, Statement st, Connection conn) { //比较规范的释放方式,比较麻烦 try { if(rs != null) rs.close(); } catch(SQLException e) { e.printStackTrace(); } finally { try { if(st != null) st.close(); } catch(SQLException e) { e.printStackTrace(); } finally { if(conn != null) try { /* * close()方法就相当于将连接放进连接池 * 即调用close()就相当于调用myDataSource.free(conn); */ conn.close(); //myDataSource.free(conn); } catch (Exception e) { e.printStackTrace(); } } } } }
在我们之前的示例中我们的MyDataSource可以实现Datasource接口,实现里面的getConnection()方法,我们想用dbcp的时候,把数据源的实现换成dbcp就行,因为这个组件也实现了DataSource接口,所以这就是面向接口编程的好处,可以换成不同的实现 ,不用改变我们其他的代码。
将DAO中的修改方法提取到抽象父类中
当你在写程序的时候,如果你发现你的代码总是有重复的地方那么就有必要封装一下了。把一段代码中的变化的部分抽取到父类里,把要用的参数传递过去,然后在实现类里面直接super调用父类的方法就可以了,记得把参数传递过去就行了。可以向外提过多个方法的重载,但是真正的代码实现只有一份。
代码就不赘述了,下面会贴出来。
使用模板方法设计模式处理DAO中的查询方法
有关模板方法设计模式,请移步我的《面向对象之继承》,有一点介绍,故在此不赘述了。
什么都不说,直接上代码:
AbstractDao类:
/* * 模板设计模式 */ public abstract class AbstractDao { public Object find(String sql, Object[] args) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = JdbcUtils.getConnection(); ps = conn.prepareStatement(sql); for(int i = 0; i < args.length; i++) { ps.setObject(i+1, args[i]); } rs = ps.executeQuery(); Object obj = null; if(rs.next()) { obj = rowMapper(rs); } return obj; } catch(SQLException e) { throw new DaoException(e.getMessage(), e); } finally { JdbcUtils.free(rs, ps, conn); } } abstract protected Object rowMapper(ResultSet rs) throws SQLException; public int update(String sql, Object[] args) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = JdbcUtils.getConnection(); ps = conn.prepareStatement(sql); for(int i = 0; i < args.length; i++) { ps.setObject(i+1, args[i]); } return ps.executeUpdate(); } catch(SQLException e) { throw new DaoException(e.getMessage(), e); } finally { JdbcUtils.free(rs, ps, conn); } } }
UserDaoImpl类:
public class UserDaoImpl extends AbstractDao { //①返回一个User对象 public User findUser(String loginName, String password) { String sql = "select id,name,birthday,money from user where name = ?"; Object[] args = new Object[] { loginName }; Object user = super.find(sql, args); return (User)user; } protected Object rowMapper(ResultSet rs) throws SQLException { User user = new User(); user.setId(rs.getInt("id")); user.setName(rs.getString("name")); user.setMoney(rs.getFloat("money")); user.setBirthday(rs.getDate("birthday")); return user; } //②返回一个字符串 public String findUserName(int id) { String sql = "select name from user where id = ?"; Object[] args = new Object[] { id }; Object user = super.find(sql, args); return ((User)user).getName(); } protected Object rowMapper1(ResultSet rs) throws SQLException { return rs.getString("name"); } public void delete(User user) { String sql = "delete from user where id = ?"; Object[] args = new Object[] { user.getId() }; super.update(sql, args); } public void update(User user) { String sql = "update user set name = ?,birthday = ?,money = ? where id = ?"; Object[] args = new Object[] {user.getName(), user.getBirthday(), user.getMoney(), user.getId()}; super.update(sql, args); } }
使用策略模式对模板方法设计模式进行改进
举例说明:
sql1 = "select name from user where id = ?";
sql2 = "select id, name, age from user where id = ?";
这两个sql查询的对象不一样,一个是只需要返回一个name属性的值就可以,而另外一个sql需要返回一个User对象,这样的话,我们上面的模板方法就又不好用了,虽然可以查出来但是性能上有损失 我只要查询一个username,你可能会把整个User对象都给我查询出来。我们可以针对每个不同的sql语句查询的内容的不同把模板方法也分解成多个不一样的能满足相应sql查询语句的方法,这就叫做策略模式(第一次听说),就是针对每一种情况都有不同的方法,来解决。
①先创建一个行映射器,不要以为这些都是废话,这是我们以后理解spring技术之JdbcTemplate的基础。
public interface RowMapper { public Object mapRow(ResultSet rs) throws SQLException; }
②创建一个基类,传入行映射器接口。
public class MyDaoTemplate { public Object find(String sql, Object[] args, RowMapper rowMapper) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = JdbcUtils.getConnection(); ps = conn.prepareStatement(sql); for(int i = 0; i < args.length; i++) { ps.setObject(i+1, args[i]); } rs = ps.executeQuery(); Object obj = null; if(rs.next()) { obj = rowMapper.mapRow(rs); } return obj; } catch(SQLException e) { throw new DaoException(e.getMessage(), e); } finally { JdbcUtils.free(rs, ps, conn); } } }
③创建UserDaoImpl类。
/* * 策略设计模式,通过类之间的组合来达到 */ public class UserDaoImpl2 { MyDaoTemplate template = new MyDaoTemplate(); public User findUser(String loginName, String password) { String sql = "select id,name,birthday,money from user where name = ?"; Object[] args = new Object[] { loginName }; Object user = this.template.find(sql, args, new UserRowMapper()); return (User)user; } public String findUserName(int id) { String sql = "select name from user where id = ?"; Object[] args = new Object[] { id }; /* 匿名内部类 Object name = this.template.find(sql, args, new RowMapper() { @Override public Object mapRow(ResultSet rs) throws SQLException { return rs.getString("name"); } }); */ Object name = this.template.find(sql, args, new StringRowMapper()); return (String)name; } } //不用匿名内部类就老老实实地写个类实现行映射器,即使麻烦 class StringRowMapper implements RowMapper { @Override public Object mapRow(ResultSet rs) throws SQLException { return rs.getString("name"); } } class UserRowMapper implements RowMapper { @Override public Object mapRow(ResultSet rs) throws SQLException { User user = new User(); user.setId(rs.getInt("id")); user.setName(rs.getString("name")); user.setMoney(rs.getFloat("money")); user.setBirthday(rs.getDate("birthday")); return user; } }
接下来我们来说spring之JdbcTemplate技术
使用JdbcTemplate工具类简化对象查询
(1)new RowMapper就是实现一个行映射器,就是对ResultSet的处理,那么内部是一个接口我们在传递参数的时候,可以用匿名类的方式实现,因为sql是我们自己写的,所以ResultSet如何映射有你自己处理。代码如下(代码是不是似曾相见啊!):
static JdbcTemplate jdbc = new JdbcTemplate(JdbcUtils.getDataSource()); static User findUser1(String name) { String sql = "select id,name,birthday,money from user where name = ?"; Object[] args = new Object[] { name }; /* * The type org.springframework.dao.DataAccessException cannot be resolved. * It is indirectly referenced from required .class files * org.springframework.dao.DataAccessException该类型不存在,不能从请求的.class文件中正确引用,应该是导入包的问题 * 解决方法:导入spring.transaction-3.0.5.jar包或spring-tx-3.2.2.RELEASE.jar包就好了。 */ Object user = jdbc.queryForObject(sql, args, new RowMapper() { @Override public Object mapRow(ResultSet rs, int rowNum) throws SQLException { User user = new User(); user.setId(rs.getInt("id")); user.setName(rs.getString("name")); user.setMoney(rs.getFloat("money")); user.setBirthday(rs.getDate("birthday")); return user; } }); return (User)user; }
第一次写可能会报异常,异常如下:
The type org.springframework.dao.DataAccessException cannot be resolved. It is indirectly referenced from required .class files
中文意思:org.springframework.dao.DataAccessException该类型不存在,不能从请求的.class文件中正确引用,应该是导入包的问题。
解决方法:导入spring.transaction-3.0.5.jar(最新的spring已改名)包或spring-tx-3.2.2.RELEASE.jar(spring-framework-4.2.5.RELEASE)包就好了。
或许还会报异常:
java.lang.ClassNotFoundException: org.springframework.beans.factory.InitializingBean
解决办法同上。
所导jar包(红框中的jar包一定注意导入):
(2)new BeanPropertyRowMapper在创建这个对象的时候需要传递一个.class文件,JdbcTemplete会通过反射技术,把ResultSet中的值取出来封装成一个对象返回回去。代码如下:
static User findUser(String name) { String sql = "select id,name,birthday,money from user where name = ?"; Object[] args = new Object[] { name }; //queryForObject()只返回一条记录 Object user = jdbc.queryForObject(sql, args, new BeanPropertyRowMapper(User.class)); return (User)user; }
JdbcTemplate类中的其他各个查询方法
在这一小节中展示了JdbcTemplete中提供的各种方法,下面列举了几个都是前面我们自己写jdbc程序的时候能实现的,只不过jdbcTemplete更进一步的做了更好的封装。代码如下:
/* * 得到Map类型的结果,如: * data:{userId=1212, name=dao name123, birthday=2016-03-29, money=20000.1} */ static Map getData(int id) { String sql = "select id as userId,name,birthday,money from user where id = "+id; return jdbc.queryForMap(sql); } //根据id查出姓名 static String getUserName(int id) { String sql = "select name from user where id = "+id; Object name = jdbc.queryForObject(sql, String.class); return (String)name; } //返回用户的总数 static int getUserCount() { String sql = "select count(*) from user"; Object count = jdbc.queryForObject(sql, Integer.class); return (Integer)count; }
使用JdbcTemplate完成数据库修改和其他功能
介绍在JdbcTemplete中update许多方法的使用,其实和其他的都是一样的,就是传递参数、sql语句之类的,在前面的示例中我们做过一个例子就是在保存一个对象之后呢,得到这个对象保存后在数据库的key值,那么在JdbcTemplete中也为我们提供了相应的方法可以实现这一需求。代码如下:
//返回插入记录的id static int addUser(final User user) { /* * 调用execute方法,传递参数的时候也是使用一个匿名类 * 然后要实现一个doInConnection的方法, * 在这个方法中它会把Connection对象创建好了之后交给你来处理,所以这也就很灵活。 这称为连接回调 */ jdbc.execute(new ConnectionCallback() {//连接回调, @Override public Object doInConnection(Connection con) throws SQLException, DataAccessException { String sql = "insert into user (name,birthday,money) values (?,?,?)"; PreparedStatement ps = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); ps.setString(1, user.getName()); ps.setDate(2, new java.sql.Date(user.getBirthday().getTime())); ps.setFloat(3, user.getMoney()); //4.执行语句 ps.executeUpdate(); /* * 保存之前user没id值 * 保存之后user有id值,即user在保存之前或之后是有区别的! */ ResultSet rs = ps.getGeneratedKeys(); if(rs.next()) user.setId(rs.getInt(1)); return null; } }); return 0; }
使用支持命名参数的JdbcTemplate
这个支持命名参数的JdbcTemplete其实就是在JdbcTemplete的基础上对参数又进行了一系列的包装,让我们在对sql语句中的参数进行传值的时候更加的方便和好用。代码如下:
public class NamedJdbcTemplate { //命名参数JdbcTemplate static NamedParameterJdbcTemplate named = new NamedParameterJdbcTemplate( JdbcUtils.getDataSource()); public static void main(String[] args) { User user = new User(); user.setMoney(10); user.setId(2); System.out.println(findUser1(user)); } static void addUser(User user) { /* * 构建sql语句,把需要传递的参数用名称的方法表示出来 需要注意的是 : * 如果用这种方式添加一个对象 ,你的values()括号中的代表参数的值必须与User类中的属性是一致的才可以 */ String sql = "insert into user (name,birthday,money) values (:name,:birthday,:money)"; /* * 直接把方法参数传递过来的User对象给他,他会通过反射的方式找到sql语句中需要设置值的属性 * 然后给他赋值,这就是为什么要求命名参数要与JavaBean中的属性名称一致的原因 */ SqlParameterSource pn = new BeanPropertySqlParameterSource(user); /* * new keyHodler对象,他可以把添加这条记录后在数据库生成的id返回回来 */ KeyHolder keyHolder = new GeneratedKeyHolder(); //调用NamedParameterJdbcTemplate的update方法 named.update(sql, pn, keyHolder); //如果主键是int型 则返回一个int值 int id = keyHolder.getKey().intValue(); user.setId(id); //如果主键是String或者是联合主键我们可以让它返回一个Map对象,然后遍历得到相应的数据 Map map = keyHolder.getKeys(); } /* * 此方法演示的是把sql语句中需要传递的参数按照名称放到Map中 * 调用方法的时候直接把Map对象传递进去,就可以给sql赋值了, * 这和针对"?"赋值来说不容易出错,因为如果采用"?"作为占位符然后赋值 顺序必须要正确否则就会出错。 */ static User findUser(User user) { String sql = "select id,name,birthday,money from user " + "where money > :m and id < :id"; Map params = new HashMap(); //params.put("n", user.getName()); params.put("m", user.getMoney()); params.put("id", user.getId()); //queryForObject()只返回一条记录 Object u = named.queryForObject(sql, params, new BeanPropertyRowMapper(User.class)); /* String sql = "select id,name,birthday,money from user " + "where name = ? and money = > ? and id < ?"; Object[] args = new Object[] { user.getName(), user.getMoney(), user.getId() }; */ return (User)u; } static User findUser1(User user) { /* * 构建sql语句,用相应的名称作为占位符: * 注意与Javabean中属性名称一样 */ String sql = "select id,name,birthday,money from user " + "where money > :money and id < :id";//名字限制为类的属性名 //直接把User对象传递给他,他会自动找到sql中需要赋值的参数并且赋值 SqlParameterSource pn = new BeanPropertySqlParameterSource(user); /* * 调用方法执行sql * new BeanPropertyRowMapper我们把相应的.class文件传递进去, * 他会把sql语句执行的结果封装成相应的对象 */ Object u = named.queryForObject(sql, pn, new BeanPropertyRowMapper(User.class)); return (User)u; } }
findUser1(user)时可能会报异常如下:
org.springframework.dao.IncorrectResultSizeDataAccessException: Incorrect result size: expected 1, actual 2
原因:从数据库里查出两条记录,而此方法期望返回一条记录,冲突出错。
spring-framework-4.2.5.RELEASE已经废弃SimpleJdbcDaoSupport及SimpleJdbcTemplate,所以在此不赘述了SimpleJdbcTemplate了,李勇老师那个时代可能还在。
使用JdbcTemplate实现DAO和用工厂灵活切换实现
用我们前面学习过的Jdbctemplate或NamedParameterJdbcTemplate来重新实现前面例子中写的Userdao这个接口,代码会简洁很多。代码如下(亲测可用):
只须在配置文件(daoconfig.properties)中修改如下:
public class UserDaoSpringImpl implements UserDao { private NamedParameterJdbcTemplate named = new NamedParameterJdbcTemplate( JdbcUtils.getDataSource()); @Override public void addUser(User user) { String sql = "insert into user (name,birthday,money) values (:name,:birthday,:money)"; SqlParameterSource pn = new BeanPropertySqlParameterSource(user); KeyHolder keyHolder = new GeneratedKeyHolder(); named.update(sql, pn, keyHolder); int id = keyHolder.getKey().intValue(); user.setId(id); } @Override public User getUser(int userId) { String sql = "select id,name,birthday,money from user where id = :id"; Map params = new HashMap(); params.put("id", userId); Object user = named.queryForObject(sql, params, new BeanPropertyRowMapper(User.class)); return (User) user; } @Override public User findUser(String loginName, String password) { String sql = "select id,name,birthday,money from user where name = :name"; Map params = new HashMap(); params.put("name", loginName); Object user = named.queryForObject(sql, params, new BeanPropertyRowMapper(User.class)); return (User)user; } @Override public void update(User user) { String sql = "update user set name = :name,birthday = :birthday,money = :money where id = :id"; SqlParameterSource params = new BeanPropertySqlParameterSource(user); named.update(sql, params); } @Override public void delete(User user) { String sql = "delete from user where id = :id"; Map params = new HashMap(); params.put("id", user.getId()); named.update(sql, params); }