JDBC(下)
- 好了,俺来终结JDBC系列了。
主要内容:
- JdbcUtils2.0版(使用数据源)
- 工厂模式重构
------
## JdbcUtils2.0版(使用数据源)
在JDBC(中)我们创建了JdbcUtils1.0版,主要目的是为了抽取冗余代码,就两个功能:
- 获取连接
- 释放连接
jdbcUtils1.0大大精简了我们的JDBC代码
JdbcUtils1.0
public class JdbcUtils {
private static Properties props = null;
// 只在JdbcUtils类被加载时执行一次!
static {
// 给props进行初始化,即加载dbconfig.properties文件到props对象中
try {
InputStream in = JdbcUtils.class.getClassLoader()
.getResourceAsStream("jdbc.properties");
props = new Properties();
props.load(in);
} catch (IOException e) {
throw new RuntimeException(e);
}
// 加载驱动类
try {
Class.forName(props.getProperty("driver"));
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
// 获取连接
public static Connection getConnection() throws SQLException {
// 得到Connection
return DriverManager.getConnection(props.getProperty("url"),
props.getProperty("username"),
props.getProperty("password"));
}
// 释放连接
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 {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}那么JdbcUtils2.0做了哪些改进呢?

JdbcUtils2.0

MyDataSource

最后,再做一步优化,把数据源中的url、username、password移到properties配置文件中:

测试

完美
------
## 工厂模式重构
假设这是Service层
public class DAOTest {
public static void main(String[] args) {
UserDao userDao = new UserDaoImpl2();
List<User> users = userDao.selectUsers(18);
for (User user : users) {
System.out.println(user);
}
}
}如果在Service层直接new了DAO层实现类,那么实现类一旦切换,还需要改动Service层代码。比如,我要把所有引用了UserDaoImpl1的地方都改为UserDaoImpl2。显然太low了。我们应该把具体的实现屏蔽,不让Service层知道具体DAO实现。建议初学者把下面的代码复制到本地IDEA玩一哈。
包结构

User
public class User {
private Long id;
private String name;
private Integer age;
private Date birthday;
//省略getter、setter...
}DaoFactory
public class DaoFactory {
private static UserDao userDao = null;
private static DaoFactory daoFactory = new DaoFactory();
private DaoFactory() {
try {
Properties prop = new Properties();
InputStream inStream = DaoFactory.class.getClassLoader()
.getResourceAsStream("daoconfig.properties");
prop.load(inStream);
//从配置文件中读取UserDao的实现类全类名
String userDaoClass = prop.getProperty("userDao");
Class userDaoImplClazz = Class.forName(userDaoClass);
//反射创建对象
userDao = (UserDao) userDaoImplClazz.newInstance();
} catch (Throwable e) {
throw new ExceptionInInitializerError(e);
}
}
public static DaoFactory getInstance() {
return daoFactory;
}
public UserDao getUserDao() {
return userDao;
}
}userDao = com.bravo.crudrefactorfinal.dao.UserDaoImpl2DAOTest
public class DAOTest {
public static void main(String[] args) {
//通过工厂得到DAO实现类,如果想换成UserDaoImpl2,修改配置即可
UserDao userDao = DaoFactory.getInstance().getUserDao();
List<User> users = userDao.selectUsers(18);
for (User user : users) {
System.out.println(user);
}
}
}UserDao
public interface UserDao {
int addUser(User user);
int update(User user);
int delete(User user);
User getUser(Long Id);
User findUser(String name, Integer age);
//新增查询方法:根据年龄查询
List<User> selectUsers(Integer age);
}UserDaoImpl2
public class UserDaoImpl2 implements UserDao {
MyJDBCTemplate jdbcTemplate = new MyJDBCTemplate();
//增
public int addUser(User user) {
String sql = "insert into t_user(name, age, birthday) values (?,?,?) ";
Object[] args = new Object[]{user.getName(), user.getAge(),
user.getBirthday()};
//调用jdbcTemplate的update方法
return jdbcTemplate.update(sql, args);
}
//删
public int update(User user) {
String sql = "update t_user set name=?, age=?, birthday=? where id=? ";
Object[] args = new Object[]{user.getName(), user.getAge(),
user.getBirthday(), user.getId()};
//调用jdbcTemplate的update方法
return jdbcTemplate.update(sql, args);
}
//改
public int delete(User user) {
String sql = "delete from t_user where id=?";
Object[] args = new Object[]{user.getId()};
//调用jdbcTemplate的update方法
return jdbcTemplate.update(sql, args);
}
public User getUser(Long id) {
String sql = "select id, name, age, birthday from t_user where id=?";
Object[] args = new Object[]{id};
//调用jdbcTemplate的query方法
List list = jdbcTemplate.query(sql, args, new RowMapper() {
public Object mapRow(ResultSet rs) throws SQLException {
User user = new User();
user.setId(rs.getInt("id"));
user.setAge(rs.getInt("age"));
user.setName(rs.getString("name"));
user.setBirthday(rs.getDate("birthday"));
return user;
}
});
return (User)list.get(0);
}
public User findUser(String name, Integer age) {
String sql = "select id, name, age, birthday from t_user where name=? and age=?";
Object[] args = new Object[]{name, age};
//调用jdbcTemplate的query方法
List list = jdbcTemplate.query(sql, args, new RowMapper() {
public Object mapRow(ResultSet rs) throws SQLException {
User user = new User();
user.setId(rs.getInt("id"));
user.setAge(rs.getInt("age"));
user.setName(rs.getString("name"));
user.setBirthday(rs.getDate("birthday"));
return user;
}
});
return (User)list.get(0);
}
public List selectUsers(Integer age) {
String sql = "select id, name, age, birthday from t_user where age=?";
Object[] args = new Object[]{age};
//调用jdbcTemplate的query方法
List list = jdbcTemplate.query(sql, args, new RowMapper() {
public Object mapRow(ResultSet rs) throws SQLException {
User user = new User();
user.setId(rs.getInt("id"));
user.setAge(rs.getInt("age"));
user.setName(rs.getString("name"));
user.setBirthday(rs.getDate("birthday"));
return user;
}
});
return list;
}
}RowMapper
public interface RowMapper {
//映射结果集
Object mapRow(ResultSet rs) throws SQLException;
}MyJDBCTemplate
public class MyJDBCTemplate {
// 增删改
public int update(String sql, Object...args) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
conn = JdbcUtils.getConnection();
// sql由调用者传入
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);
}
}
//查询
public List<Object> query(String sql, Object[] args, RowMapper rowMapper) {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
List list = new ArrayList();
try {
conn = JdbcUtils.getConnection();
// sql由调用者传入
ps = conn.prepareStatement(sql);
// 遍历设置模板参数
for (int i = 0; i < args.length; i++)
ps.setObject(i + 1, args[i]);
rs = ps.executeQuery();
Object obj = null;
// 映射规则由子类传入
while (rs.next()) {
Object o = rowMapper.mapRow(rs);
list.add(o);
}
return list;
} catch (SQLException e) {
throw new DaoException(e.getMessage(), e);
} finally {
JdbcUtils.free(rs, ps, conn);
}
}
}DaoException
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
}
}JdbcUtils2.0
public class JdbcUtils {
// 初始化一个数据源
private static MyDataSource dataSource = new MyDataSource();
// 获取连接
public static Connection getConnection() throws SQLException {
// 从数据源获取Connection并返回
return dataSource.getConnection();
}
// 获取数据源
public static MyDataSource getDataSource() {
return dataSource;
}
// 释放连接
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 {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}MyDataSource
/**
* 数据源,包含一个连接池
* 连接池里的存放的Connection以及用户从数据源拿走的其实都是【代理连接】,它的close方法其实是“归还池中”
*/
public class MyDataSource {
private static Properties props = null;
// 数据库信息,用于连接数据库
static {
// 给props进行初始化,即加载dbconfig.properties文件到props对象中
try {
InputStream in = JdbcUtils.class.getClassLoader()
.getResourceAsStream("jdbc.properties");
props = new Properties();
props.load(in);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 池中初始连接数(创建DataSource时池中就有5个连接)
private static int initCount = 5;
// 池中最小空闲连接数,小于这个数量就要创建连接并加入池中
private static int minIdleCount = 3;
// 池中最大允许存放的连接数
private static int maxIdleCount = 10;
// 当前池中连接数
private static int currentIdleCount = 0;
// 数据源创建连接的次数
private static int createCount = 0;
// LinkedList充当连接池,removeFirst取出连接,addLast归还连接
private final static LinkedList<Connection> connectionsPool = new LinkedList<Connection>();
/**
* 空参构造,按照initCount预先创建一定数量的连接存入池中
*/
public MyDataSource() {
try {
for (int i = 0; i < initCount; i++) {
// 创建RealConnection
Connection realConnection = DriverManager.getConnection(props.getProperty("url"),
props.getProperty("username"),
props.getProperty("password"));
// 将RealConnection传入createProxyConnection(),得到代理连接并加入池中,currentIdleCount++
connectionsPool.addLast(this.createProxyConnection(realConnection));
currentIdleCount++;
}
System.out.println("-------连接池初始化结束,共初始化" + currentIdleCount + "个Connection-------");
} catch (SQLException e) {
throw new ExceptionInInitializerError(e);
}
}
/**
* 公共方法,外界通过MyDataSource调用此方法得到代理连接
*
* @return
* @throws SQLException
*/
public Connection getConnection() throws SQLException {
//同步代码
synchronized (connectionsPool) {
// 连接池中还有空闲连接,从池中取出,currentIdleCount--
if (currentIdleCount > 0) {
currentIdleCount--;
if (currentIdleCount < minIdleCount) {
// 创建RealConnection
Connection realConnection = DriverManager.getConnection(props.getProperty("url"),
props.getProperty("username"),
props.getProperty("password"));
// 将RealConnection传入createProxyConnection(),得到代理连接并加入池中,currentIdleCount++
connectionsPool.addLast(this.createProxyConnection(realConnection));
currentIdleCount++;
}
return connectionsPool.removeFirst();
}
/*
* 如果连接池没有空闲连接(都被用户拿走了),那么就再生成连接。比如第11个。
* 不用考虑maxIdleCount,它指的是连接池最多存放多少个空闲连接,而不是数据源能生成多少个
* 如果这第11个连接后期调用close,程序会判断当前连接池中的连接数是否大于maxIdleCount,如果已经存满了就直接销毁第11个连接,不会放入池中
* */
Connection realConnection = DriverManager.getConnection(props.getProperty("url"),
props.getProperty("username"),
props.getProperty("password"));
// 数据源创建连接后直接返回,没有加入池,也没有从池中取出,currentIdleCount不变
return this.createProxyConnection(realConnection);
}
}
/**
* 私有方法,用于生成代理连接
* 调用时机:数据源初始化,以及用户调用dataSource.getConnection时
*
* @param realConn
* @return
* @throws SQLException
*/
private Connection createProxyConnection(Connection realConn) throws SQLException {
// 这句代码仅仅是为了把realConn转为final,这样才能在匿名对象invocationHandler中使用
final Connection realConnection = realConn;
// 动态代理:返回Connection代理对象
Connection proxyConnection = (Connection) Proxy.newProxyInstance(
this.getClass().getClassLoader(),
realConnection.getClass().getInterfaces(),
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 对close()方法进行拦截
if ("close".equals(method.getName())) {
// 连接池空闲连接数小于最大空闲数,说明还能存得下,于是连接被归还到池中
if (MyDataSource.currentIdleCount < MyDataSource.maxIdleCount) {
MyDataSource.connectionsPool.addLast((Connection) proxy);
MyDataSource.currentIdleCount++;
// 返回1表示成功
return 1;
} else {
// 当前连接池满了,这个连接已经存不下,所以只能销毁(调用目标对象的close)
realConnection.close();
// 返回1表示成功
return 1;
}
}
return method.invoke(realConnection, args);
}
});
System.out.println("新建Connection(" + ++MyDataSource.createCount + "):" + proxyConnection);
return proxyConnection;
}
}------
JDBC完结,感谢传智播客李勇老师2008年录制的JDBC视频,以及崔希凡老师、方立勋老师,还有尚硅谷的佟刚老师。
往常,我会把程序尽量写完。但是这次,我想把这个任务交给正在阅读本文的各位读者。上面的程序,仍然存在问题:
- MyJDBCTemplate中query的返回值设置成List存在局限性。如果用户想映射出Map呢?所以用Object最好

- 每次都让用户自己写一个匿名内部类实在太烦了,而且findUser和getUser方法返回值都是User,会重复。返回值类型其实是可以穷举的,比如单个Bean,List<Bean>、Map、List<Map>等。我们能否预先定义几个映射器供用户使用?

以上问题,大家可以思考一下如何实现。具体可以参考传智播客方立勋老师的讲解(只看最后两个视频):
链接:https://pan.baidu.com/s/1XHXRMoUgeNC2sA9O74ueCg
提取码:3lqk
我写的jdbc最终版代码(含建表语句):
链接:https://pan.baidu.com/s/1xq5uMlVK6-VQPMP7BgJ4wg
提取码:d9yt
项目结构
我也不知道有多少人能看到这里。但是JDBC系列如果真的全部自己手写一遍,对自己的提升是非常大的。强烈推荐。
本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。