一、使用模板方法设计模式简化开发
模板方法设计模式,执行一个程序有很多步骤,将每次都要执行的共有的提取出来放到一个抽象父类中,变化的部分通过让子类传递参数过来或将这部分抽象为抽象方法让子类通过继承的方式去实现.
我们的UserDaoJdbcImpl 有大量重复的代码,使用模板设计模式重构如下:
//定义抽象类,提取公共部分 public abstract class AbstractDao { // 查 public Object find(String sql, Object[] args) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; Object obj = 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(); while (rs.next()) { obj = rowMapper(rs); } } catch (SQLException e) { throw new DaoException(e.getMessage(), e); } finally { JdbcUtils.free(rs, ps, conn); } return obj; } // 将变化的部分,让子类去实现 public abstract Object rowMapper(ResultSet rs);
//增删改 public void 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]); } } catch (SQLException e) { throw new DaoException(e.getMessage(), e); } finally { JdbcUtils.free(rs, ps, conn); } } }
public class UserDaoImpl extends AbstractDao implements UserDao{ 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); } public User findUser(String loginName, String password) { String sql = "select id, name, money, birthday from user where name=?"; Object[] args = new Object[] { loginName }; Object user = super.find(sql, args); return (User) user; } public 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; } }
这种方式有一个问题就是UserDaoImp类中只能有一个rowMapper方法,如果还有一个查询方法要映射,则无法实现只能在写一个类,相当不灵活.既然变化的sql和参数可以从子类传递那么映射也可以传递,结合策略模式重构上面代码如下.
二、使用策略模式对模板方法设计模式进行改进
//没有抽象方法了,也没有必要定义为抽象类 public class MyTemplete { //RowMapper定义为一个接口,可以有不同的实现,相当于不同的策略 public Object find(String sql,Object[] args,RowMapper rowMapper) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; Object obj = 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(); while (rs.next()) { obj = rowMapper.rowMapper(rs); } } catch (SQLException e) { throw new DaoException(e.getMessage(), e); } finally { JdbcUtils.free(rs, ps, conn); } return obj; } } public interface RowMapper{ Object rowMapper(ResultSet rs)throws SQLException; }
public class UserDaoImpl implements UserDao{ MyTemplete myTemplete = new MyTemplete(); //定义一个模板 public User findUser(String loginName, String password) { String sql = "select id, name, money, birthday from user where name=?"; Object[] args = new Object[] { loginName }; Object user = myTemplete.find(sql, args,new RowMapper(){ @Override public 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; } }); return (User) user; } }
三、Apache—DBUtils框架
//使用dbutils完成数据库的crud public class Demo1 { @Test public void insert() throws SQLException{ QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "insert into users(id,name,password,email,birthday) values(?,?,?,?,?)"; Object params[] = {2,"bbb","123","aa@sina.com",new Date()}; runner.update(sql, params); } @Test public void update() throws SQLException{ QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "update users set email=? where id=?"; Object params[] = {"aaaaaa@sina.com",1}; runner.update(sql, params); } @Test public void delete() throws SQLException{ QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "delete from users where id=?"; runner.update(sql, 1); } @Test public void find() throws SQLException{ QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "select * from users where id=?"; User user = (User) runner.query(sql, 1, new BeanHandler(User.class)); System.out.println(user.getEmail()); } @Test public void getAll() throws Exception{ QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "select * from users"; List list = (List) runner.query(sql, new BeanListHandler(User.class)); System.out.println(list); } @Test public void batch() throws SQLException{ QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "insert into users(id,name,password,email,birthday) values(?,?,?,?,?)"; Object params[][] = new Object[3][5]; for(int i=0;i<params.length;i++){ //3 params[i] = new Object[]{i+1,"aa"+i,"123",i + "@sina.com",new Date()}; } runner.batch(sql, params); } }
//测试dbutils的各个结果集处理器,只是一部分 public class Demo2 { @Test public void test1() throws SQLException{ QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "select * from users"; Object result[] = (Object[]) runner.query(sql, new ArrayHandler()); System.out.println(result[0]); System.out.println(result[1]); } @Test public void test2() throws SQLException{ QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "select * from users"; List list = (List) runner.query(sql, new ArrayListHandler()); System.out.println(list); } @Test public void test3() throws SQLException{ QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); String sql = "select * from users"; List list = (List) runner.query(sql, new ColumnListHandler1("name")); System.out.println(list); } }
ps:采用DBUtils后,jdbcUtils工具类中获取连接和释放连接的方法也不需要了,只需提供获取数据源的方法(如果进行做事务处理还需要).
四、jdbc应用事务管理
1、使用如上所示的jdbcUtils时,我们无需管理连接的获取和关闭,在每次进行完一条sql的执行后连接会自动归还连接池.但是要进行事务处理的话就要手动获取和关闭连接,因为同一个事务必须使用同一个数据库连接,转账案例dao层代码如下:
//从a--->b帐户转100元 public void transfer() throws SQLException{ Connection conn = null; try{ conn = JdbcUtils.getConnection();//连接池中获取连接 conn.setAutoCommit(false); QueryRunner runner = new QueryRunner(); String sql1 = "update account set money=money-100 where name='aaa'"; runner.update(conn,sql1); String sql2 = "update account set money=money+100 where name='bbb'"; runner.update(conn,sql2); conn.commit(); }finally{ if(conn!=null){ conn.close();//连接放回连接池 } } }
ps:dao层做事务管理的话就把业务逻辑放在了dao层,违背三层架构设计思想,dao层只能做简单的增删改查,于是要将事务管理放在service层,代码如下:
public void transfer1(int sourceid,int targetid,double money) throws SQLException{ Connection conn = null; try{ conn = JdbcUtils.getConnection(); conn.setAutoCommit(false); AccountDao dao = new AccountDao(conn); //把当前conn传递给dao Account a = dao.find(sourceid); //select Account b = dao.find(targetid); //select a.setMoney(a.getMoney()-money); b.setMoney(b.getMoney()+money); dao.update(a); //update dao.update(b);//update conn.commit(); }finally{ if(conn!=null) conn.close(); } }
2、更优雅的做法,使用ThreadLocal(*重要),ThreadLocal可以实现线程内的数据共享,ThreadLocal其实是一个大大的Map集合,set方法就是往map集合中存数据,map的key是当前线程的名称,ThreadLocal API如下,只有三个方法:
T get() Returns the value in the current thread's copy of this thread-local variable. void remove() Removes the current thread's value for this thread-local variable. void set(T value) Sets the current thread's copy of this thread-local variable to the specified value.
使用ThreadLocal后代码如下:
//QueryRunner 的各种更新和查询可以接收一个数据库连接,简直就是为事务处理设置的,不处理事务没必要传连接 public class AccountDao { private Connection conn; public AccountDao(Connection conn) { this.conn = conn; } public void update(Account a) { try { QueryRunner runner = new QueryRunner(); String sql = "update account set money=? where id=?"; Object params[] = { a.getMoney(), a.getId() }; runner.update(JdbcUtils.getConnection(), sql, params); } catch (Exception e) { throw new RuntimeException(e); } } public Account find(int id) { try { QueryRunner runner = new QueryRunner(); String sql = "select * from account where id=?"; return (Account) runner.query(JdbcUtils.getConnection(), sql, id, new BeanHandler(Account.class)); } catch (Exception e) { throw new RuntimeException(e); } } }
//用上ThreadLocal的事务管理 public void transfer2(int sourceid,int targetid,double money) throws SQLException{ try{ JdbcUtils.startTransaction(); AccountDao dao = new AccountDao(); Account a = dao.find(sourceid); //select Account b = dao.find(targetid); //select a.setMoney(a.getMoney()-money); b.setMoney(b.getMoney()+money); dao.update(a); //update dao.update(b);//update JdbcUtils.commitTransaction(); }finally{ JdbcUtils.closeConnection(); } }
public class JdbcUtils { private static DataSource ds; static { try { Properties prop = new Properties(); InputStream in = JdbcUtils.class.getClassLoader() .getResourceAsStream("dbcpconfig.properties"); prop.load(in); BasicDataSourceFactory factory = new BasicDataSourceFactory(); ds = factory.createDataSource(prop); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static DataSource getDataSource() { return ds; } // 不进行事务处理的话上面的代码就够了,下面用来进行事务处理 private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>(); public static Connection getConnection() throws SQLException { try { // 得到当前线程上绑定的连接 Connection conn = tl.get(); if (conn == null) { // 代表线程上没有绑定连接 conn = ds.getConnection(); tl.set(conn); } return conn; } catch (Exception e) { throw new RuntimeException(e); } } public static void startTransaction() { try { // 得到当前线程上绑定连接开启事务 Connection conn = tl.get(); if (conn == null) { // 代表线程上没有绑定连接 conn = ds.getConnection(); tl.set(conn); } conn.setAutoCommit(false); } catch (Exception e) { throw new RuntimeException(e); } } public static void commitTransaction() { try { Connection conn = tl.get(); if (conn != null) { conn.commit(); } } catch (Exception e) { throw new RuntimeException(e); } } public static void closeConnection() { try { Connection conn = tl.get(); if (conn != null) { conn.close(); } } catch (Exception e) { throw new RuntimeException(e); } finally { tl.remove();// 千万注意,解除当前线程上绑定的链接(从threadlocal容器中移除对应当前线程的链接) } } }
ps:ThreadLocal用处很多,很多框架中使用它存储对象,比如现在servlet中使用外部定义的对象可以有三种方式,通过参数传递给它,通过JNDI容器,通过ThreadLocal.
五、servlet层事务管理
1、如果service层事务管理不能满足需求,需要在servlet层进行事务处理,使用如上代码也可实现,将JdbcUtils.startTransaction(); JdbcUtils.commitTransaction(); JdbcUtils.closeConnection();放在servlet中即可.
2、如果单servlet也不能满足需求,比如实现servlet转发后还需要事务处理,则要可以使用Filter,在Filter中给ThreadLocal绑定连接.
ps:也就是实现jdbc事务必须使用同一个数据库连接,只需要将ThreadLocal与数据库连接的绑定往上层提即可.
***事务的处理使用springJDBC好像比较方便,以后再看.
六、jdbc多表操作
1、员工和部门是一对多的关系,老师和学生是多对多的关系,设计表的时候一定要注意.如下老师学生代码:
public void add(Teacher t) throws SQLException { QueryRunner runner = new QueryRunner(JdbcUtils.getDataSource()); //1`.取出老师存老师表 String sql = "insert into teacher(id,name,salary) values(?,?,?)"; Object params[] = {t.getId(),t.getName(),t.getSalary()}; runner.update(sql, params); //2.取出老师所有学生的数据,存学生表 Set<Student> set = t.getStudents(); for(Student s : set){ sql = "insert into student(id,name) values(?,?)"; params = new Object[]{s.getId(),s.getName()}; runner.update(sql, params); //3.更新中间表,说明老师和学生的关系 sql = "insert into teacher_student(teacher_id,student_id) values(?,?)"; params = new Object[]{t.getId(),s.getId()}; runner.update(sql, params); } }
推理:这段代码必须要加事务处理,那么在dao层中又加入了逻辑代码,所以应该将这段代码在dao层中分开写,在service层中进行事务处理.
七、springJDBC使用
public class JdbcTemplateTest { static JdbcTemplate jdbc = new JdbcTemplate(JdbcUtils.getDataSource()); //查询 static User findUser(String name) { String sql = "select id, name, money, birthday from user where name=?"; Object[] args = new Object[] { name }; Object user = jdbc.queryForObject(sql, args, new RowMapper() { 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; } //BeanPropertyRowMapper利用原数据和反射直接封装到User中 static User findUser1(String name) { String sql = "select id, name, money, birthday from user where name=?"; Object[] args = new Object[] { name }; Object user = jdbc.queryForObject(sql, args, new BeanPropertyRowMapper( User.class)); return (User) user; } //特殊的跟新要拿到插入的组建,其它正常更新略 static int addUser(final User user) { jdbc.execute(new ConnectionCallback() { 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()); ps.executeUpdate(); ResultSet rs = ps.getGeneratedKeys(); if (rs.next()) user.setId(rs.getInt(1)); return null; } }); return 0; } }
ps:以后补充springJDBC.