JDBC学习笔记——增删改查
1、数据库准备
要用JDBC操作数据库,第一步当然是建立数据表:
1
2
3
4
5
6
|
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、释放资源。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
public
static
void
test()
throws
SQLException{
// 1.注册驱动
Class.forName(
"com.mysql.jdbc.Driver"
);
// 2.建立连接 url格式 - JDBC:子协议:子名称//主机名:端口/数据库名?属性名=属性值&…
// 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类,该类会封装数据库连接步骤中的第一步、第二步及第六步操作,分别是注册驱动,建立连接及释放资源操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
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 {
}
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类进行增、删、改、查操作:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
//增加操作
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类如下:
1
2
3
4
5
6
7
8
|
public
class
User {
private
int
id;
private
String name;
private
Date birthday;
private
float
money;
//getters and setters
}
|
按照"面向接口编程而非面向实现编程"的原则,我们可以定义数据表操作的接口如下:
1
2
3
4
5
6
|
public
interface
UserDao {
public
void
addUser(User user);
public
User getUser(
int
userId);
public
void
update(User user);
public
void
delete(User user);
}
|
然后我们使用JDBC方式实现这个接口如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
|
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两个方法。所以我们可以把上面的增删改查修改为如下形式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
|
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对象耦合的,而且耦合部分只有封装结果集的部分,我们可以把这一部分代码抽象成一个接口,让调用方传入,这样就可以避免这部分耦合,所以定义接口如下:
1
2
3
|
public
interface
RowMapper {
public
Object mapRow(ResultSet rs)
throws
SQLException;
}
|
然后我们修改第四节的UserDaoUtils对象如下,并重命名为MyJdbcTemplate:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
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接口如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
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的不同实现,但是却一直没讲如何使用这些实现,要使用这些方法首先应该创建对象,最简单的创建方法应该是像下面这样:
1
|
UserDao userDao =
new
UserDaoJdbcImpl();
|
但是这种把实现硬编码进代码中不是很优雅,如果我们想修改实现,就必须重新编译代码,更好的我们使用配置文件定义实现类,创建时读取配置文件决定应该使用哪个实现。配置文件的格式使用Java Properties格式,配置文件的内容如下:
1
|
userDaoClass=cn.test.UserDaoJdbcImpl3
|
我们将使用工厂模式创建一个DaoFactory对象,该对象有一个createUserDao()方法,该方法将读返回一个UserDao接口的实现,方法的实现,我们可以选择每次都创建一个全新的返回,也可以选择第一次创建然后,缓存起来,之后就直接返回缓存对象的方法,在这里我们选择第二种,该对象的实现如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
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类,对上面代码进行简单测试:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
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());
}
}
|