脑子太笨,必须得记录下来一些文字,方便回来查询。
这是我的第二次学习JDBC的笔记,看的是传智播客——李勇老师的JDBC系列,已看到第23集。
分析在实际项目中该如何应用JDBC
一个简单用户相关的数据访问层
- J2EE三层架构简介:表示层 、业务逻辑层、数据访问层,三层之间用接口隔离。
-
定义domain对象User,定义存取用户的接口。
-
用JDBC实现接口。
- 用配置文件(properties)和反射实现与具体类的耦合。
用图来表示:
在真实的项目开发中,我们能不能返回ResultSet对象呢? 如下例代码:
static ResultSet read() throws SQLException { Connection conn = null; Statement st = null; ResultSet rs = null; try { //2.建立连接 conn = JdbcUtils.getConnection(); //3.创建语句 st = conn.createStatement(); //4.执行语句 rs = st.executeQuery("select id,name,birthday,money from user"); //5.处理结果 while(rs.next()) { System.out.println(rs.getObject("id")+"\t"+rs.getObject("name")+ "\t"+rs.getObject("birthday")+"\t"+rs.getObject("money")); } return rs;//连接关闭,Statement、ResultSet都失效了。 } finally { JdbcUtils.free(rs, st, conn); } }
在main()方法中调用:
ResultSet rs = read();
这个其实是不可以的,当Connection关闭之后,ResultSet中的数据你就拿不到了(Statement、ResultSet都失效了)。如果我们要进行传值需要定义个domain对象。
我们重点关注数据访问层的代码如何书写
1、定义domain对象User以及存取用户的接口:
import cn.itcast.jdbc.domain.User; public interface UserDao { public void addUser(User user); public User getUser(int userId); public User findUser(String loginName, String password); public void update(User user); public void delete(User user); }
import java.util.Date; public class User { private int id; private String name; private Date birthday; private float money; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public float getMoney() { return money; } public void setMoney(float money) { this.money = money; } }
2、用JDBC实现接口。在实现的过程中,准确一点说会出现SQLException。关于这个异常的处理,很多工作2年的程序员都不知道怎么样处理,所以对于此异常,定当重视,通常我们结合Service层讲解DAO层的异常处理的方式。
就拿addUser(user)方法来说,中间是有可能出现SQLException的,在处理异常的时候,不能catch住异常之后什么都不做,最起码要打印一下堆栈,最好的方式是将这个异常转化为RuntimeException抛出。做法如下:
建立一个DaoException继承RuntimeException,如下:
public class DaoException extends RuntimeException { /** * */ private static final long serialVersionUID = 1L; public DaoException() { // TODO Auto-generated constructor stub } public DaoException(String message) { super(message); // TODO Auto-generated constructor stub } public DaoException(Throwable cause) { super(cause); // TODO Auto-generated constructor stub } public DaoException(String message, Throwable cause) { super(message, cause); // TODO Auto-generated constructor stub } public DaoException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { super(message, cause, enableSuppression, writableStackTrace); // TODO Auto-generated constructor stub } }
之后JDBC实现接口的过程中,就抛出DaoException异常,如下(代码太多,折叠之):
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import cn.itcast.jdbc.JdbcUtils; import cn.itcast.jdbc.dao.DaoException; import cn.itcast.jdbc.dao.UserDao; import cn.itcast.jdbc.domain.User; public class UserDaoJdbcImpl implements UserDao { @Override public void addUser(User user) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { //2.建立连接 conn = JdbcUtils.getConnection(); //3.创建语句 String sql = "insert into user (name,birthday,money) values (?,?,?)"; ps = conn.prepareStatement(sql); ps.setString(1, user.getName()); ps.setDate(2, new java.sql.Date(user.getBirthday().getTime())); ps.setFloat(3, user.getMoney()); //4.执行语句 ps.executeUpdate(); } catch(SQLException e) { throw new DaoException(e.getMessage(), e); } finally { JdbcUtils.free(rs, ps, conn); } } @Override public User getUser(int userId) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; User user = null; try { //2.建立连接 conn = JdbcUtils.getConnection(); //3.创建语句 String sql = "select id,name,birthday,money from user where id = ?"; ps = conn.prepareStatement(sql); ps.setInt(1, userId); //4.执行语句 rs = ps.executeQuery(); //5.处理结果 while(rs.next()) { /* * 优化①处 */ user = mappingUser(rs); } } catch(SQLException e) { throw new DaoException(e.getMessage(), e); } finally { JdbcUtils.free(rs, ps, conn); } return user; } @Override public User findUser(String loginName, String password) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; User user = null; try { //2.建立连接 conn = JdbcUtils.getConnection(); //3.创建语句 String sql = "select id,name,birthday,money from user where name = ?"; ps = conn.prepareStatement(sql); ps.setString(1, loginName); //4.执行语句 rs = ps.executeQuery(); //5.处理结果 while(rs.next()) { /* * 优化①处 */ user = mappingUser(rs); } } catch(SQLException e) { throw new DaoException(e.getMessage(), e); } finally { JdbcUtils.free(rs, ps, conn); } return user; } private User mappingUser(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; } @Override public void update(User user) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { //2.建立连接 conn = JdbcUtils.getConnection(); //3.创建语句 String sql = "update user set name = ?,birthday = ?,money = ? where id = ?"; ps = conn.prepareStatement(sql); ps.setString(1, user.getName()); ps.setDate(2, new java.sql.Date(user.getBirthday().getTime())); ps.setFloat(3, user.getMoney()); ps.setInt(4, user.getId()); //4.执行语句 ps.executeUpdate(); } catch(SQLException e) { throw new DaoException(e.getMessage(), e); } finally { JdbcUtils.free(rs, ps, conn); } } @Override public void delete(User user) { Connection conn = null; Statement st = null; ResultSet rs = null; try { //2.建立连接 conn = JdbcUtils.getConnection(); //3.创建语句 st = conn.createStatement(); //4.执行语句 String sql = "delete from user where id = "+user.getId(); st.executeUpdate(sql); } catch(SQLException e) { throw new DaoException(e.getMessage(), e); } finally { JdbcUtils.free(rs, st, conn); } } }
用配置文件(properties)和反射实现与具体类的耦合(?),此时就要用到工厂模式了。
DaoFactory类如下(视频说代码很经典,谁知道呢?):
import java.io.InputStream; import java.util.Properties; public class DaoFactory { /* * 注意两个static属性之间的顺序 * 如果搞反,那么属性userDao始终为空! */ private static UserDao userDao = null; private static DaoFactory instance = new DaoFactory(); private DaoFactory() { try { Properties prop = new Properties(); /* * 方式一 * 缺点:路径是写死的,如果我们的配置文件改变了地方,就找不到文件了 。 */ //FileInputStream fis = new FileInputStream("src/daoconfig.properties"); /* * 方式二 * 优点:用类加载器(马丹,涉及到反射技术,又是一个要补的地方)的方法 * 来得到一个文件的输入流,只要这个文件在classpath路径下(其实编译的xxx.class文件和配置文件 * 就在根目录下bin目录中,而次bin目录被自动加载到classpath变量值中(别问我why?我也不知道))就能找到。 */ InputStream fis = DaoFactory.class.getClassLoader()//类加载器(???) .getResourceAsStream("daoconfig.properties");//daoconfig.properties只要在classpath路径下,就可以加载进来,更加灵活 prop.load(fis); String userDaoClass = prop.getProperty("userDaoClass"); /* * 涉及到反射技术 */ Class clazz = Class.forName(userDaoClass);//将类加载到JVM userDao = (UserDao)clazz.newInstance();//实例化一个对象 //System.out.println(userDao); } catch (Throwable e) { throw new ExceptionInInitializerError(e); } } public static DaoFactory getInstance() { return instance; } public UserDao getUserDao() { //System.out.println(userDao); return userDao; } }
整体的框架图应该如这幅模样:
JDBC事务处理
关于事务,我已经在《浅谈事务》一文中详细记录过,在此不赘述了。我们只关注JDBC是如何处理事务的。
示例1(用代码来体现):
import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class TxIsolateTest { public static void main(String[] args) throws SQLException { test(); } static void test() throws SQLException { Connection conn = null; Statement st = null; ResultSet rs = null; try { conn = JdbcUtils.getConnection(); /* * 设置事务为手动提交,一说也叫打开事务。 */ conn.setAutoCommit(false); /* * 设置事务的隔离级别,这是一个超麻烦的问题,李勇老师也是敷衍过去! */ conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); String sql = "update user set money = money - 10 where id = 1"; st = conn.createStatement(); st.executeUpdate(sql); sql = "select money from user where id = 2"; rs = st.executeQuery(sql); float money = 0.0f; if(rs.next()) { money = rs.getFloat("money"); } if(money > 400) throw new RuntimeException("已经超过最大值!"); sql = "update user set money = money + 10 where id = 2"; st.executeUpdate(sql); /* * 提交事务 */ conn.commit(); } catch (SQLException e) { if(conn != null) /* * 回滚事务,rollback()中不加任何参数,即为回滚所有操作 */ conn.rollback(); throw e; } finally { JdbcUtils.free(rs, st, conn); } } }
示例2:
import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Savepoint; import java.sql.Statement; public class SavePointTest { public static void main(String[] args) throws SQLException { test(); } static void test() throws SQLException { Connection conn = null; Statement st = null; ResultSet rs = null; Savepoint sp = null; try { conn = JdbcUtils.getConnection(); conn.setAutoCommit(false); st = conn.createStatement(); String sql = "update user set money = money - 10 where id = 1"; st.executeUpdate(sql); /* * 设置保存点 */ sp = conn.setSavepoint(); sql = "update user set money = money - 10 where id = 3"; st.executeUpdate(sql); sql = "select money from user where id = 2"; rs = st.executeQuery(sql); float money = 0.0f; if(rs.next()) { money = rs.getFloat("money"); } if(money > 300) throw new RuntimeException("已经超过最大值!"); sql = "update user set money = money + 10 where id = 2"; st.executeUpdate(sql); conn.commit(); } catch (RuntimeException e) { if(conn != null && sp != null) { conn.rollback(sp);//回滚到保存点上去,再提交,这样保证到保存点之前那些操作都是有效的(只回滚一部分操作) conn.commit(); } throw e; } catch (SQLException e) { if(conn != null) conn.rollback(); throw e; } finally { JdbcUtils.free(rs, st, conn); } } }
JTA分布式事务的简要介绍(李勇老师也是敷衍过去了)
跨越多个数据源的事务,使用JTA容器(是啥,不知道?)实现事务。
分成两阶段提交。
代码格式总是如此:
javax.transaction.UserTransaction tx = (UserTransaction)ctx.lookup(“jndi(?)Name"); tx.begin(); //connection1 connection2 (可能来自不同的数据库)… tx.commit();//tx.rollback();