一、數據庫準備 java
要用JDBC操做數據庫,第一步固然是創建數據表:mysql
CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, `name` varchar(45) DEFAULT NULL, `birthday` date DEFAULT NULL, `money` double DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
二、JDBC鏈接數據庫的基本步驟 sql
JDBC鏈接數據庫包含如下幾個基本步驟:一、註冊驅動 ;二、創建鏈接(Connection);三、建立SQL語句(Statement);四、執行語句;五、處理執行結果(ResultSet);六、釋放資源。數據庫
public static void test() throws SQLException{ // 1.註冊驅動 Class.forName("com.mysql.jdbc.Driver"); // 2.創建鏈接 url格式 - JDBC:子協議:子名稱//主機名:端口/數據庫名?屬性名=屬性值&… Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc", "root", ""); // 3.建立語句 Statement st = conn.createStatement(); // 4.執行語句 ResultSet rs = st.executeQuery("select * from user"); // 5.處理結果 while (rs.next()) { System.out.println(rs.getObject(1) + "\t" + rs.getObject(2) + "\t" + rs.getObject(3) + "\t" + rs.getObject(4)); } // 6.釋放資源 rs.close(); st.close(); conn.close(); }
三、簡單的增刪改查 編程
第二節的代碼有一個問題,若是咱們在執行代碼時拋出異常,那麼Connection就沒法關閉了,因此咱們應該把關閉資源操做放入finally中,這樣就不管如何都會關閉這些數據庫鏈接資源。同時咱們還會擴展程序功能,上面的例子只是展現了一個查詢操做,接下來將會展現最經常使用的增、刪、改、查四個操做。首先介紹一個JdbcUtils類,該類會封裝數據庫鏈接步驟中的第一步、第二步及第六步操做,分別是註冊驅動,創建鏈接及釋放資源操做。緩存
public final class JdbcUtils { static { try { Class.forName("com.mysql.jdbc.Driver"); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(e); } } private JdbcUtils() { } public static Connection getConnection() throws SQLException { return DriverManager.getConnection("jdbc:mysql://localhost:3306/jdbc", "root", ""); } 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(); } } } } }
能夠看到,這個類的構造函數是一個私有構造函數,因此咱們將沒法建立這個類的實例。在靜態初始化域,咱們進行了註冊驅動操做,靜態初始化域只會在類加載的時候執行一次,這樣能夠保證只要加載了這個類,咱們會且僅會註冊一次驅動。而後getConnection()方法封裝了創建鏈接操做,free(rs, st, conn)方法封裝了釋放資源操做。接下來能夠看看如何使用JdbcUtils類進行增、刪、改、查操做:安全
//增長操做 void create() throws SQLException { Connection conn = null; Statement st = null; ResultSet rs = null; try { conn = JdbcUtils.getConnection(); st = conn.createStatement(); int i = st .executeUpdate("insert into user(name,birthday, money) values ('name1', '1987-01-01', 400) "); System.out.println("i=" + i); } finally { JdbcUtils.free(rs, st, conn); } } //刪除操做 void delete() throws SQLException { Connection conn = null; Statement st = null; ResultSet rs = null; try { conn = JdbcUtils.getConnection(); st = conn.createStatement(); int i = st.executeUpdate("delete from user where id>4"); System.out.println("i=" + i); } finally { JdbcUtils.free(rs, st, conn); } } //修改操做 void update() throws SQLException { Connection conn = null; Statement st = null; ResultSet rs = null; try { conn = JdbcUtils.getConnection(); st = conn.createStatement(); int i = st.executeUpdate("update user set money=money+10 "); System.out.println("i=" + i); } finally { JdbcUtils.free(rs, st, conn); } } //查詢操做 void read() throws SQLException { Connection conn = null; Statement st = null; ResultSet rs = null; try { conn = JdbcUtils.getConnection(); st = conn.createStatement(); rs = st.executeQuery("select id, name, money, birthday from user"); while (rs.next()) { System.out.println(rs.getObject("id") + "\t" + rs.getObject("name") + "\t" + rs.getObject("birthday") + "\t" + rs.getObject("money")); } } finally { JdbcUtils.free(rs, st, conn); } }
四、面向對象封裝增刪改查 app
第三節的例子只是爲了展現如何使用JDBC進行增刪改查操做,在項目中真正使用時,咱們是不會像上面的例子這樣簡單使用的,Java是面向對象的,因此咱們通常會使用面向對象的思想對操做進行封裝。首先,其實對於數據表每一條數據,咱們均可以認爲它是一個對象實例,例如此例中咱們定義的數據表User有id,name,birthday和money四個屬性,對應的咱們能夠建立User類以下:框架
public class User { private int id; private String name; private Date birthday; private float money; //getters and setters }
按照"面向接口編程而非面向實現編程"的原則,咱們能夠定義數據表操做的接口以下:ide
public interface UserDao { public void addUser(User user); public User getUser(int userId); public void update(User user); public void delete(User user); }
而後咱們使用JDBC方式實現這個接口以下:
public class UserDaoJdbcImpl implements UserDao { public void addUser(User user) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = JdbcUtils.getConnection(); 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()); ps.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } finally { JdbcUtils.free(rs, ps, conn); } } public void delete(User user) { Connection conn = null; Statement st = null; ResultSet rs = null; try { conn = JdbcUtils.getConnection(); st = conn.createStatement(); String sql = "delete from user where id=" + user.getId(); st.executeUpdate(sql); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } finally { JdbcUtils.free(rs, st, conn); } } public User getUser(int userId) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; User user = null; try { conn = JdbcUtils.getConnection(); String sql = "select id, name, money, birthday from user where id=?"; ps = conn.prepareStatement(sql); ps.setInt(1, userId); rs = ps.executeQuery(); while (rs.next()) { user = new User(); user.setId(rs.getInt("id")); user.setName(rs.getString("name")); user.setMoney(rs.getFloat("money")); user.setBirthday(rs.getDate("birthday")); } } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } finally { JdbcUtils.free(rs, ps, conn); } return user; } public void update(User user) { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = JdbcUtils.getConnection(); 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()); ps.executeUpdate(); } catch (SQLException e) { throw new RuntimeException(e.getMessage(), e); } finally { JdbcUtils.free(rs, ps, conn); } } }
能夠看到,真正核心的代碼其實和第二節的代碼很相像,可是按照這種風格寫的代碼擴展性更好,若是哪一天咱們不打算使用JDBC,而改用Hibernate鏈接數據庫,使用接口編程只需修改實現,不須要修改其餘部分,大大減少了修改難度。
五、傳入sql執行
須要說明的是,上面的代碼使用了PreparedStatement對象,PrepareStatement是預編譯的Statement對象,它在建立的時候就會把sql的大致框架搭建起來,把一些變量用佔位符表示,使用時,咱們再設置這些佔位符的值。PrepareStatement最大的特色是能夠防止sql注入,更安全,因此再須要拼接用戶輸入的場景,推薦使用PrepareStatement。
第四節代碼的編碼風格相似Hibernate,Hibernate的不少操做都是須要傳入對象的,可是這種傳遞對象的方式靈活性不高,例如update()方法,咱們把User對象上的全部屬性都更新了,可是可能咱們只想更新birthday一個屬性,更新其餘屬性有點多餘,因此更好的方法應該是傳入sql語句,而不是一個User對象。再仔細觀察,咱們發現,其實咱們最終只是調用了Statement上的兩個方法,分別是executeUpdate和executeQuery兩個方法。因此咱們能夠把上面的增刪改查修改成以下形式:
public class UserDaoUtils { private UserDaoUtils(){ } static User executeQuery(String sql, Object[] params) throws SQLException { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; User user = null; try { conn = JdbcUtils.getConnection(); ps = conn.prepareStatement(sql); for (int i = 1; i <= params.length; i++) { ps.setObject(i, params[i - 1]); } rs = ps.executeQuery(); while (rs.next()) { user = new User(); user.setId(rs.getInt("id")); user.setBirthday(rs.getDate("birthday")); user.setMoney(rs.getFloat("money")); user.setName(rs.getString("name")); } } finally { JdbcUtils.free(rs, ps, conn); } return user; } static int executeUpdate(String sql, Object[] params) throws SQLException { Connection conn = null; PreparedStatement ps = null; int rs = 0; try { conn = JdbcUtils.getConnection(); ps = conn.prepareStatement(sql); for (int i = 1; i <= params.length; i++) { ps.setObject(i, params[i - 1]); } rs = ps.executeUpdate(); } finally { JdbcUtils.free(null, ps, conn); } return rs; } } public class UserDaoJdbcImpl2 implements UserDao{ @Override public void addUser(User user) { try { UserDaoUtils.executeUpdate("insert into user(name,birthday, money) values (?,?,?)", new Object[]{user.getName(), user.getBirthday(), user.getMoney()}); } catch (SQLException e) { e.printStackTrace(); } } @Override public User getUser(int userId) { User user = null; try { user = UserDaoUtils.executeQuery("select id, name, money, birthday from user where id=?", new Object[]{userId}); } catch (SQLException e) { e.printStackTrace(); } return user; } @Override public void update(User user) { try { UserDaoUtils.executeUpdate("update user set name=?, birthday=?, money=? where id=?", new Object[]{user.getName(), user.getBirthday(), user.getMoney(), user.getId()}); } catch (SQLException e) { e.printStackTrace(); } } @Override public void delete(User user) { try { UserDaoUtils.executeUpdate("delete from user where id=?", new Object[]{user.getId()}); } catch (SQLException e) { e.printStackTrace(); } } }
首先咱們定義了一個UserDaoUtils對象,該對象包含兩個方法,分別是executeQuery()和executeUpdate()方法,這兩個方法均包含兩個參數,分別是sql語句及sql語句的參數。而後咱們定義了UserDaoJdbcImpl2類,該類使用UserDaoUtils實現了UserDao接口,相較於UserDaoJdbcImpl簡化了不少。
六、利用結果集元數據封裝對象
上面的UserDaoJdbcImpl2和UserDaoUtils的代碼都已經很簡潔了,可是有個問題,若是咱們想封裝其餘對象的JDBC操做,那麼咱們將不得不從新定義一對Utils和Impl,這個實際上是重複勞動,那麼咱們有沒有什麼方法能夠避免這些重複勞動呢?Impl對象是必須定義的,由於咱們須要實現不一樣的對象,若是想少定義一些對象,那麼就只能不定義Utils對象。查看UserUtils的exectueQuery()和executeUpdate()方法,發現只有executeQuery()方法是與User對象耦合的,並且耦合部分只有封裝結果集的部分,咱們能夠把這一部分代碼抽象成一個接口,讓調用方傳入,這樣就能夠避免這部分耦合,因此定義接口以下:
public interface RowMapper { public Object mapRow(ResultSet rs) throws SQLException; }
而後咱們修改第四節的UserDaoUtils對象以下,並重命名爲MyJdbcTemplate:
public class MyJdbcTemplate { private MyJdbcTemplate(){} public static Object executeQuery(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 RuntimeException(e.getMessage(), e); } finally { JdbcUtils.free(rs, ps, conn); } } public static int executeUpdate(String sql, Object[] params) throws SQLException { Connection conn = null; PreparedStatement ps = null; int rs = 0; try { conn = JdbcUtils.getConnection(); ps = conn.prepareStatement(sql); for (int i = 1; i <= params.length; i++) { ps.setObject(i, params[i - 1]); } rs = ps.executeUpdate(); } finally { JdbcUtils.free(null, ps, conn); } return rs; } }
能夠看到,如今咱們的executeQuery()方法已經與User對象解耦了,因此整個對象都已經與User對象解耦,是一個通用方法,咱們可使用該對象實現UserDao接口以下:
public class UserDaoJdbcImpl3 implements UserDao { @Override public void addUser(User user) { try { MyJdbcTemplate.executeUpdate( "insert into user(name,birthday, money) values (?,?,?)", new Object[] { user.getName(), user.getBirthday(), user.getMoney() }); } catch (SQLException e) { e.printStackTrace(); } } @Override public User getUser(int userId) { User user = null; try { user = (User) MyJdbcTemplate.executeQuery( "select id, name, money, birthday from user where id=?", new Object[] { userId }, new 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; } }); } catch (Exception e) { e.printStackTrace(); } return user; } @Override public void update(User user) { try { MyJdbcTemplate.executeUpdate( "update user set name=?, birthday=?, money=? where id=?", new Object[] { user.getName(), user.getBirthday(), user.getMoney(), user.getId() }); } catch (SQLException e) { e.printStackTrace(); } } @Override public void delete(User user) { try { MyJdbcTemplate.executeUpdate("delete from user where id=?", new Object[] { user.getId() }); } catch (SQLException e) { e.printStackTrace(); } } }
UserDaoJdbcImpl3的實現與第四節UserDaoJdbcImpl2的實現十分類似,只有getUser()方法與UserDaoJdbcImpl2不一樣,在UserDaoJdbcImpl3總,咱們不只要傳遞sql語句以及sql參數,咱們還須要傳遞RowMapper對象,該對象可以幫助咱們把查詢結果封裝成一個User對象。
七、用配置文件實現與具體類的解耦
咱們一直在講UserDao的不一樣實現,可是卻一直沒講如何使用這些實現,要使用這些方法首先應該建立對象,最簡單的建立方法應該是像下面這樣:
UserDao userDao = new UserDaoJdbcImpl();
可是這種把實現硬編碼進代碼中不是很優雅,若是咱們想修改實現,就必須從新編譯代碼,更好的咱們使用配置文件定義實現類,建立時讀取配置文件決定應該使用哪一個實現。配置文件的格式使用Java Properties格式,配置文件的內容以下:
userDaoClass=cn.test.UserDaoJdbcImpl3
咱們將使用工廠模式建立一個DaoFactory對象,該對象有一個createUserDao()方法,該方法將讀返回一個UserDao接口的實現,方法的實現,咱們能夠選擇每次都建立一個全新的返回,也能夠選擇第一次建立而後,緩存起來,以後就直接返回緩存對象的方法,在這裏咱們選擇第二種,該對象的實現以下:
public class DaoFactory { private static UserDao userDao = null; private static DaoFactory instance = new DaoFactory(); private DaoFactory() { } public static DaoFactory getInstance() { return instance; } public UserDao createUserDao() { if (userDao == null) { try { Properties prop = new Properties(); InputStream inStream = DaoFactory.class.getClassLoader() .getResourceAsStream("daoconfig.properties"); prop.load(inStream); String userDaoClass = prop.getProperty("userDaoClass"); Class<?> clazz = Class.forName(userDaoClass); userDao = (UserDao) clazz.newInstance(); } catch (Throwable e) { throw new ExceptionInInitializerError(e); } } return userDao; } }
最後,編寫一個UserDaoTest類,對上面代碼進行簡單測試:
public class UserDaoTest { public static void main(String[] args) { UserDao userDao = DaoFactory.getInstance().createUserDao(); User user = new User(); user.setBirthday(new Date()); user.setMoney(234242); user.setName("xxxx"); userDao.addUser(user); User u = userDao.getUser(1); System.out.println(u.getId() + "\t" + u.getName() + "\t" + u.getMoney() + "\t" + u.getBirthday()); } }