1、數據庫准備
要用JDBC操作數據庫,第一步當然是建立數據表:
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;
2、JDBC連接數據庫的基本步驟
JDBC連接數據庫包含以下幾個基本步驟:1、注冊驅動 ;2、建立連接(Connection);3、創建SQL語句(Statement);4、執行語句;5、處理執行結果(ResultSet);6、釋放資源。
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();
}
3、簡單的增刪改查
第二節的代碼有一個問題,如果我們在執行代碼時拋出異常,那么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);
}
}
4、面向對象封裝增刪改查
第三節的例子只是為了展示如何使用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
}
按照"面向接口編程而非面向實現編程"的原則,我們可以定義數據表操作的接口如下:
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連接數據庫,使用接口編程只需修改實現,不需要修改其他部分,大大減小了修改難度。
5、傳入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簡化了很多。
6、利用結果集元數據封裝對象
上面的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對象。
7、用配置文件實現與具體類的解耦
我們一直在講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());
}
}
