JDBC学习
1.获取JDBC的连接对象
1.加载注册驱动
Class.forName("com.mysql.jdbc.Driver");
当执行这一步时,会把Driver这份字节码加载进JVM,然后执行该字节码的静态代码块
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
2.获取连接
通过DriverManager类的getConnection方法获取连接对象
@Test
public void test1() throws Exception {
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn =
DriverManager.getConnection("jdbc:mysql:///stu", "root", "password");
Thread.sleep(5000);
}
连接过后出现这样的错误:
java.sql.SQLException: The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more
找了资料之后发现意思是 mysqljdbc . driver被弃用了新的驱动类是“com.mysql.cjdbc.driver”。驱动程序通过SPI自动注册,而手动加载类通常是不必要的,只要将com.mysql.jdbc.Driver 改为com.mysql.cj.jdbc.Driver 即可。
另一个异常是系统时区出了问题,只要在URL之后加上"?serverTimezone=GMT%2B8"就可以了( GMT%2B8代表东八区)
或者在Navicat的命令行中输入:set global time_zone='+8:00';
另外,在mysql的my.ini文件中的[mysqld]中加入default-time_zone = '+8:00'语句,再重启MySQL服务就可以了。
在MySQL命令行再次查看系统时间:show variables like '%time_zone%'; 和 select now();
然后我们用mysql命令行的show processlist 检测连接。
3.操作JDBC
- 加载注册驱动
- 获取连接对象
- 创建语句对象
- 执行SQL语句
- 释放资源
@Test
public void test() throws Exception {
String sql = "CREATE TABLE test (id bigint(10) PRIMARY KEY AUTO_INCREMENT, name varchar(20), age int(2))";
//1.加载注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接对象
Connection conn =
DriverManager.getConnection("jdbc:mysql:///stu", "root", "password");
//3.创建语句对象
Statement st = conn.createStatement();
//4.执行SQL语句
int row = st.executeUpdate(sql);
st.close();;
conn.close();
System.out.println(row);
}
运行后发现数据库中就会多出一张表test
4.异常处理
@Test
public void testHandleException() {
String sql = "CREATE TABLE test (id bigint(10) PRIMARY KEY AUTO_INCREMENT, name varchar(20), age int(2))";
//声明需要关闭的资源
Connection conn = null;
Statement st = null;
try {
//1.加载注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接对象
conn = DriverManager.getConnection("jdbc:mysql:///stu","root","password");
//3.创建语句对象
st = conn.createStatement();
//4.执行SQL语句
st.executeUpdate(sql);
} catch (Exception e) {
e.printStackTrace();
} finally {
//5.释放资源
try {
if (st != null) {
st.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
5.DML操作
仍然使用executeUpdate,没什么好说的,再写一遍熟练一下
@Test
public void testInsert() throws Exception{
String sql = "INSERT INTO test (name,age) values('Bobbui',19)";
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///stu","root","password");
Statement st = conn.createStatement();
int row = st.executeUpdate(sql);
System.out.println(row);
st.close();;
conn.close();
}
@Test
public void testUpdate() throws Exception{
String sql = "DELETE FROM test WHERE name='Bobbui'";
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///stu","root","password");
Statement st = conn.createStatement();
st.executeUpdate(sql);
st.close();
conn.close();
}
6.DQL操作
有个地方比较有意思,在Java中,只有JDBC和JPA中的索引是从1开始的。
ResultSet可以用迭代器取出对象:
List<String> list = Arrays.asList("A", "B", "C", "D");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
public class DQLTest {
@Test
public void test() throws Exception {
String sql = "SELECT score.Student_id 学号,Student_name 姓名,SUM(Grade) 总分\n" +
"FROM score,student\n" +
"WHERE (score.Student_id=student.Student_id) GROUP BY student.Student_name";
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection("jdbc:mysql:///studentinfo", "root", "password");
Statement st = conn.createStatement();
ResultSet rs = st.executeQuery(sql);
//处理结果集
while (rs.next()) {
String stuNum = rs.getString("学号");
String stuName = rs.getString("姓名");
int stuScore = rs.getInt("总分");
System.out.println(stuNum + "," + stuName + "," + stuScore);
}
//关闭资源
rs.close();
st.close();
conn.close();
}
}
得到的结果为:
7.DAO介绍
DAO(Data Access Object):数据存取对象,位于业务逻辑和持久化数据之间,能够实现对持久化数据的访问
DAO在实体类与数据库之间起着转换器的作用,能够把实体类转换为数据库中的记录
DAO模式是作用:
- 隔离业务逻辑代码和数据访问代码
- 隔离不同数据库的实现
DAO模式的组成部分:
- DAO接口
- DAO实现类
- 实体类
- 数据库连接和关闭工具类
分层开发:
一种化大为小,分而治之的软件开发方法
分层的特点:
1.每一层都有自己的职责
2.上层不用关心下次的实现细节,上层通过下层提供的对外接口来使用其功能
3.上一层调用下一层的功能,下一层不能调用上一层的功能
分层开发的好处:
1.各层专注于自己功能的实现,便于提高质量
2.便于分工协作,提高开发效率
3.便于代码复用
4.便于程序扩展
分层原则:
封装性原则
每个层次向外公开接口,但是隐藏内部细节
顺序访问原则
下一层为上一层服务,但不使用上层的服务
分层结构中,不同层之间通过实体类传输数据
8.DAO设计
DAO组件
- DAO接口
- DAO实现类
- DAO测试类
包名规范
package 域名倒写.模块名称.组件名称
package com.Bryan.smis.domain 存储所有的domain类
package com.Bryan.smis.dao 存储所有的DAO接口
package com.Bryan.smis.dao.impl 存储所有的DAO接口的实现类
package com.Bryan.smis.test 存储DAO组件的测试类
Student类:
public class Student {
private String id;
private String name;
private Integer age;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", age=" + age + '}';
}
}
IStudentDAO接口:
//封装Student对象的CRUD操作
public interface IStudentDAO {
/**
* 保存操作
* @param stu 学生对象
*/
void save(Student stu);
/**
* 删除操作
* @param id 被删除学生的主键值
*/
void delete(String id);
/**
* 更新操作
* @param id 被查询学生的主键值
* @param newStu 如果id存在,返回该学生对象,否则返回null
*/
void update(String id,Student newStu);
/**
* 查询指定Id的学生对象
* @param id 被查询学生的主键值
* @return 如果id存在,返回该学生对象,否则返回null
*/
Student get(String id);
/**
* 查询并返回所有学生对象
* @return 如果结果集为空,则返回null
*/
List<Student> listAll();
}
StudentDAOImpl类:
public class StudentDAOImpl implements IStudentDAO {
public void save(Student stu) {
//String sql = "INSERT INTO student (Student_id,sex,Student_name,age) VALUES ('20178013','M','Cherry',19)";
StringBuilder sb = new StringBuilder(80);
sb.append("INSERT INTO student (Student_id,sex,Student_name,age) VALUES ('");
sb.append(stu.getId());
sb.append("','");
sb.append(stu.getSex());
sb.append("','");
sb.append(stu.getName());
sb.append("',");
sb.append(stu.getAge());
sb.append(")");
//System.out.println(sb);
//声明资源
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
//1.加载注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接对象
conn = DriverManager.getConnection("jdbc:mysql:///studentinfo", "root", "password");
//3.创建语句对象
st = conn.createStatement();
//4.执行SQL
st.executeUpdate(sb.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (st != null) {
st.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
//DELETE FROM student WHERE Student_id = '20178013'
public void delete(String id) {
StringBuilder sb = new StringBuilder(80);
sb.append("DELETE FROM student WHERE Student_id = '");
sb.append(id);
sb.append("'");
//System.out.println(sb);
//声明资源
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
//1.加载注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接对象
conn = DriverManager.getConnection("jdbc:mysql:///studentinfo", "root", "password");
//3.创建语句对象
st = conn.createStatement();
//4.执行SQL
st.executeUpdate(sb.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (st != null) {
st.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
//UPDATE student SET Student_name = 'XXX',age = 12 WHERE Student_id = '20178013'
public void update(String id, Student newStu) {
StringBuilder sb = new StringBuilder(80);
sb.append("UPDATE student SET Student_name = '");
sb.append(newStu.getName());
sb.append("',age = ");
sb.append(newStu.getAge());
sb.append(" WHERE Student_id = '");
sb.append(id);
sb.append("'");
//System.out.println(sb);
//声明资源
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
//1.加载注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接对象
conn = DriverManager.getConnection("jdbc:mysql:///studentinfo", "root", "password");
//3.创建语句对象
st = conn.createStatement();
//4.执行SQL
st.executeUpdate(sb.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (st != null) {
st.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public Student get(String id) {
String sql = "SELECT * FROM student WHERE Student_id = " + id;
//声明资源
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
//1.加载注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接对象
conn = DriverManager.getConnection("jdbc:mysql:///studentinfo", "root", "password");
//3.创建语句对象
st = conn.createStatement();
//4.执行SQL
rs = st.executeQuery(sql);
//5.处理结果集
//--------------------------------
if (rs.next()) {
Student stu = new Student();
//获取当前光标所在的行的列值,并设置到stu中
stu.setAge(rs.getInt("age"));
stu.setId(rs.getString("Student_id"));
stu.setName(rs.getString("Student_name"));
return stu;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (rs != null) {
rs.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (st != null) {
st.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return null;
}
public List<Student> listAll() {
List<Student> list = new ArrayList<>();
String sql = "SELECT * FROM student";
//声明资源
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
//1.加载注册驱动
Class.forName("com.mysql.cj.jdbc.Driver");
//2.获取连接对象
conn = DriverManager.getConnection("jdbc:mysql:///studentinfo", "root", "password");
//3.创建语句对象
st = conn.createStatement();
//4.执行SQL
rs = st.executeQuery(sql);
//5.处理结果集
//--------------------------------
while (rs.next()) {
Student stu = new Student();
//获取当前光标所在的行的列值,并设置到stu中
stu.setAge(rs.getInt("age"));
stu.setId(rs.getString("Student_id"));
stu.setName(rs.getString("Student_name"));
list.add(stu);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (rs != null) {
rs.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (st != null) {
st.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
return list;
}
}
StudentDAOTest类:
public class StudentDAOTest {
//依赖DAO对象
private IStudentDAO dao = new StudentDAOImpl();
@Test
public void testSave() {
Student stu = new Student();
stu.setName("Dans");
stu.setAge(19);
dao.save(stu);
}
@Test
public void testDelete() {
dao.delete("20178013");
}
@Test
public void testUpdate() {
Student stu = new Student();
stu.setName("lrh");
stu.setSex("F");
stu.setAge(18);
dao.update("20178013",stu);
}
@Test
public void testGet() {
Student stu = dao.get("20010103");
System.out.println(stu);
}
@Test
public void testListAll() {
List<Student> stus = dao.listAll();
for (Student stu : stus) {
System.out.println(stu);
}
}
}
到现在为止,已经完成了DAO模式的封装,但是我们发现在StudentDAOImpl中重复代码太多,不符合软件代码的高内聚低耦合原则,下面我们进行重构。
9.重构设计
重构(Refactoring)就是通过调整程序代码,改善软件的质量、性能,使其程序的设计模式和架构更加合理,提高软件的扩展性和维护性。
抽取出JDBCUtil类
我们发现连接数据库的几行代码每次都要重写一遍,因此考虑将其改成成员变量,但是当DAO太多的时候,这样也会造成代码冗余,因此我们考虑封装成一个Util类。
/**
* JDBC工具类
*/
public class JdbcUtil {
//连接数据库的四要素
public static String driverClassName = "com.mysql.cj.jdbc.Driver";
public static String url = "jdbc:mysql:///studentinfo";
public static String userName = "root";
public static String password = "password";
}
//---------------------------------------------------------
//1.加载注册驱动
Class.forName(JdbcUtil.driverClassName);
//2.获取连接对象
conn = DriverManager.getConnection(JdbcUtil.url, JdbcUtil.userName, JdbcUtil.password);
但是考虑到封装的Util字段为公有,安全性不高。因此我们考虑将创建Connection对象也封装到Util类。然而,每次都需要重新注册驱动,没有必要,因此我们在JdbcUtil中的静态代码块中进行驱动注册,并将关闭资源进行封装。
/**
* JDBC工具类
*/
public class JdbcUtil {
//连接数据库的四要素
private static String driverClassName = "com.mysql.cj.jdbc.Driver";
private static String url = "jdbc:mysql:///studentinfo";
private static String userName = "root";
private static String password = "password";
//静态代码块,当JdbcUtil加载进JVM就开始执行
static {
try {
Class.forName(driverClassName);
} catch (Exception e) {
e.printStackTrace();
}
}
//创建并返回一个Connection对象
public static Connection getConnection() {
try {
return DriverManager.getConnection(url, userName, password);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
//释放资源
public static void close(Connection conn, Statement st, ResultSet rs) {
try {
if (st != null) {
st.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (conn != null) {
conn.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
封装完的StudentDAOImpl类如下:
public class StudentDAOImpl implements IStudentDAO {
public void save(Student stu) {
//String sql = "INSERT INTO student (Student_id,sex,Student_name,age) VALUES ('20178013','M','Cherry',19)";
StringBuilder sb = new StringBuilder(80);
sb.append("INSERT INTO student (Student_id,sex,Student_name,age) VALUES ('");
sb.append(stu.getId());
sb.append("','");
sb.append(stu.getSex());
sb.append("','");
sb.append(stu.getName());
sb.append("',");
sb.append(stu.getAge());
sb.append(")");
//System.out.println(sb);
//声明资源
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtil.getConnection();
//3.创建语句对象
st = conn.createStatement();
//4.执行SQL
st.executeUpdate(sb.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtil.close(conn, st, null);
}
}
//DELETE FROM student WHERE Student_id = '20178013'
public void delete(String id) {
StringBuilder sb = new StringBuilder(80);
sb.append("DELETE FROM student WHERE Student_id = '");
sb.append(id);
sb.append("'");
//声明资源
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtil.getConnection();
//3.创建语句对象
st = conn.createStatement();
//4.执行SQL
st.executeUpdate(sb.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtil.close(conn, st, null);
}
}
//UPDATE student SET Student_name = 'XXX',age = 12 WHERE Student_id = '20178013'
public void update(String id, Student newStu) {
StringBuilder sb = new StringBuilder(80);
sb.append("UPDATE student SET Student_name = '");
sb.append(newStu.getName());
sb.append("',age = ");
sb.append(newStu.getAge());
sb.append(" WHERE Student_id = '");
sb.append(id);
sb.append("'");
//声明资源
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtil.getConnection();
//3.创建语句对象
st = conn.createStatement();
//4.执行SQL
st.executeUpdate(sb.toString());
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtil.close(conn, st, null);
}
}
public Student get(String id) {
String sql = "SELECT * FROM student WHERE Student_id = " + id;
//声明资源
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtil.getConnection();
//3.创建语句对象
st = conn.createStatement();
//4.执行SQL
rs = st.executeQuery(sql);
//5.处理结果集
//--------------------------------
if (rs.next()) {
Student stu = new Student();
//获取当前光标所在的行的列值,并设置到stu中
stu.setAge(rs.getInt("age"));
stu.setId(rs.getString("Student_id"));
stu.setName(rs.getString("Student_name"));
return stu;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtil.close(conn, st, rs);
}
return null;
}
public List<Student> listAll() {
List<Student> list = new ArrayList<>();
String sql = "SELECT * FROM student";
//声明资源
Connection conn = null;
Statement st = null;
ResultSet rs = null;
try {
conn = JdbcUtil.getConnection();
//3.创建语句对象
st = conn.createStatement();
//4.执行SQL
rs = st.executeQuery(sql);
//5.处理结果集
//--------------------------------
while (rs.next()) {
Student stu = new Student();
//获取当前光标所在的行的列值,并设置到stu中
stu.setAge(rs.getInt("age"));
stu.setId(rs.getString("Student_id"));
stu.setName(rs.getString("Student_name"));
list.add(stu);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
JdbcUtil.close(conn, st, rs);
}
return list;
}
}
这样看上去代码更加简洁了。
但是,这些还是可以继续进行封装。
抽取db.properties
在JdbcUtil中存在硬编码,还是不利于维护,我们可以将配置信息专门提取到配置文件中去,因此,我们抽取出了db.properties文件,存放于source folder目录中:
修改后的JdbcUtil一部分:
private static Properties p = new Properties();
//静态代码块,当JdbcUtil加载进JVM就开始执行
static {
try {
//加载和读取db.properties
InputStream inStream = Thread.currentThread().getContextClassLoader()
.getResourceAsStream("db.properties");
p.load(inStream);
Class.forName(p.getProperty("driverClassName"));
} catch (Exception e) {
e.printStackTrace();
}
}
//创建并返回一个Connection对象
public static Connection getConnection() {
try {
return DriverManager.getConnection(
p.getProperty("url"),
p.getProperty("username"),
p.getProperty("password"));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
但是SQL语句看上去还是过于冗余,而且用字符串拼接来进行查询,不易于查询和修改。因此我们使用PreparedStatement(预编译语句对象)。
让我们深入分析该Demo,发现在DAO方法中,每次都新创建一个Connection,然后关闭,所消耗的资源非常大。所以我们考虑可以重复利用Connection对象,解决方案:数据库连接池(DataSource)。
对于DML(增删改)操作来说,代码模板其实是相同的,DQL(查询)操作代码模板其实也是相同的,我们考虑将其重构。因此我们重构出Jdbc Template类,封装DML和DQL操作的通用模板。
我们发现SQL语句是很麻烦的,我们可以考虑不写SQL语句而能操作数据库。因此我们考虑模拟Hibernate框架。
具体内容请关注JAVA学习之DAO优化。