• 好了,俺来终结JDBC系列了。

主要内容:

  • JdbcUtils2.0版(使用数据源)
  • 工厂模式重构

------

## JdbcUtils2.0版(使用数据源)

JDBC(中)我们创建了JdbcUtils1.0版,主要目的是为了抽取冗余代码,就两个功能:

  • 获取连接
  • 释放连接

imgjdbcUtils1.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做了哪些改进呢?

img

JdbcUtils2.0

img

MyDataSource

img

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

img

测试

img

完美

------

## 工厂模式重构

假设这是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玩一哈。

包结构

img

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;
    }
}

daoconfig.properties

userDao = com.bravo.crudrefactorfinal.dao.UserDaoImpl2

DAOTest

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最好

img

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

img

以上问题,大家可以思考一下如何实现。具体可以参考传智播客方立勋老师的讲解(只看最后两个视频):

链接:https://pan.baidu.com/s/1XHXRMoUgeNC2sA9O74ueCg

提取码:3lqk

我写的jdbc最终版代码(含建表语句):

链接:https://pan.baidu.com/s/1xq5uMlVK6-VQPMP7BgJ4wg

提取码:d9yt

img项目结构

我也不知道有多少人能看到这里。但是JDBC系列如果真的全部自己手写一遍,对自己的提升是非常大的。强烈推荐。