概述
代码的重构
在我平时的代码开发中,各个部分的功能确实能够实现,但是缺少重构,代码显得十分的臃肿,扩展性差、不易于后续的维护。
下面就简单的Java JDBC的数据访问操作,进行重构的尝试。
声明
本博客内容,借鉴于书籍《Spring5 核心原理与30个类手写实战》,谭勇德著
原始代码
本人用MySQL作为数据,并在springmodel数据库中创建一张名为t_student的数据表。
package Test.StudentService;
import Test.Entity.student;
import Test.Utils.DbUtil;
import java.sql.*;
/**
* @Classname stuService
* @Description TODO
* @Date 2020/3/24 18:30
* @Created by ASUS
*/
public class stuService implements IService{
/**
*功能描述:将传入的学生信息加入到数据库中
*@param stu the student need to be saved.
*Author Jason
*Date 2020-03-24 18:33
*/
@Override
public void save_student(student stu){
String sql = "INSERT INTO t_student(name,age) VALUES(?,?)";
Connection connection = null;
PreparedStatement preparedStatement = null;
int age = stu.getStu_Age();
String name = stu.getStu_Name();
try {
Class.forName("com.mysql.jdbc.Driver");//注册驱动
connection = DriverManager.getConnection//获取连接
("jdbc:mysql://localhost:3306/springmodel?serverTimezone=UTC&characterEncoding=utf8","root","");
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setObject(1,name);
preparedStatement.setObject(2,age);
preparedStatement.execute();//执行SQL语句
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
} finally {
try{
if(preparedStatement != null){
preparedStatement.close();
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
try{
if(connection != null){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
/**
*功能描述:根据id值在数据库中检索并删除数据
*@param id the id of the student that need to be deleted.
*Author Jason
*Date 2020-03-24 18:36
*/
@Override
public void delete_stu_byId(int id){
String sql = "DELETE FROM t_student WHERE age=?";
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
Class.forName("com.mysql.jdbc.Driver");//注册驱动
connection = DriverManager.getConnection//获取连接
("jdbc:mysql://localhost:3306/springmodel?serverTimezone=UTC&characterEncoding=utf8","root","");
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setObject(1,id);
preparedStatement.execute();//执行SQL语句
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}finally {
try{
if(preparedStatement != null){
preparedStatement.close();
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
try{
if(connection != null){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
/**
*功能描述:更新学生的信息数据
*@param stu the student that need to be pull into database.
*Author Jason
*Date 2020-03-24 18:37
*/
@Override
public void update_stu(student stu){
String sql = "UPDATE t_student SET name=? WHERE age=?";
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
Class.forName("com.mysql.jdbc.Driver");//注册驱动
connection = DriverManager.getConnection//获取连接
("jdbc:mysql://localhost:3306/springmodel?serverTimezone=UTC&characterEncoding=utf8","root","");
preparedStatement = connection.prepareStatement(sql);
preparedStatement.setObject(1,stu.getStu_Name());
preparedStatement.setObject(2,stu.getStu_Age());
preparedStatement.execute();//执行SQL语句
} catch (ClassNotFoundException | SQLException e) {
e.printStackTrace();
}finally {
try{
if(preparedStatement != null){
preparedStatement.close();
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
try{
if(connection != null){
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
重构优化过程
可以看出仅仅是简单的增删改的操作,代码行数100+,臃肿得不要太厉害。
其实我们细看代码,可以发现其中有很多的代码是可以抽取出来的,提高程序的复用率。
在每个方法中,都有重复的注册数据库驱动,获取数据库连接的操作代码,相似度很高,于是可以将其抽取到一个工具类DbUtil中。同时观察代码:
Class.forName("com.mysql.jdbc.Driver");//注册驱动
connection = DriverManager.getConnection//获取连接
("jdbc:mysql://localhost:3306/springmodel?serverTimezone=UTC&characterEncoding=utf8","root","");
得出:有关数据连接的各种数据如:端口localhost:3306,数据库名称:springmodel,数据库的用户名和密码等都显式的出现在代码中,不符合软件架构设计的开闭原则(Open-Closed Pinciple,OCP)(指的是一个软件实体,如类、函数和模块等,对扩展开放,对修改关闭。)在需要对使用的数据名称改变时,就需要对源代码修改,可维护性低;另外,数据库的用户名和密码直接出现在代码中,不仅对后期的账户和密码的维护不利,还存在着一定的隐患。
于是可以将url,name,password等其他信息写入到一个mysql.properties文件中,程序通过读取配置信息,来获得建立连接需要的信息,不仅提高了代码的可扩展性,还一定程度上降低了安全隐患。同样对于不同的SQL语句也进行上述的设计,可以方便后续程序的维护。根据以上思考,建立工具类:
/**
* @Classname DbUtil
* @Description TODO
* @Date 2020/3/25 18:22
* @Created by ASUS
*/
public class DbUtil implements Serializable {
private static Properties properties = null;
static {//静态的代码块,一旦产生该类的对象,自动执行该代码块
/*使用static 静态初始化代码块时候注意应用
*try{....}catch(Exception e){...},
*否则会发生类初始化异常*/
try{
//
ClassLoader loader = Thread.currentThread().getContextClassLoader();
//
InputStream is = loader.getResourceAsStream("");
InputStream is = new FileInputStream(new File("config/mysql.properties"));
properties = new Properties();
properties.load(is);
//加载数据库的properties文件
//加载数据库注册驱动
Class.forName(properties.getProperty("driver"));
} catch (Exception e) {
e.printStackTrace();
}
}
/**
*功能描述:返回数据库的连接connection
* 声明为类的静态方法
*/
public static Connection getConnect(){
Connection connection = null;
String url = properties.getProperty("url");
String name = properties.getProperty("user");
String pwd = properties.getProperty("password");
try {
connection = DriverManager.getConnection(url,name,pwd);
} catch (SQLException e) {
e.printStackTrace();
}
return
connection;
}
}
mysql.properties
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/springmodel?serverTimezone=UTC&characterEncoding=utf8
user=root
password=
serverIP=127.0.0.1
SQL.properties
INSERT=INSERT INTO t_student(name,age) VALUES (?,?)
UPDATE=UPDATE t_student SET name=? WHERE age=?
DELETE=DELETE FROM t_student WHERE age=?
QUERY_AGE=SELECT * FROM t_student WHERE age=?
QUERY_ALL=SELECT * FROM t_student
然后在调用的方法中直接调用getConnect()方法即可得到与数据库的连接。程序的可读性也得到提高。到这里似乎已经完成了优化,但仔细观察,代码中除去已经优化的建立连接的部分,仍然有很多的代码类似,比如在进行数据库操作之后,关闭相应的连接,释放资源的代码即可以抽出到工具类中。在DML操作中,除了SQL语句的设置不同,其他部分基本一致,可以进行相同部分抽取,不同部分以参数的形式传入。这些抽取可以构建一个模板类
JDBCTemplate.java
/**
* @Classname JDBCtemplate
* @Description TODO
* @Date 2020/3/26 9:38
* @Created by ASUS
*/
public class JDBCtemplate {
//Query template统一查询模版(DQL),借助泛型方便功能的扩展
@Nullable
public static <E> E Query(String sql, IRowMapper<E> mapper, Object...params){
//
List<student> studentList = new ArrayList<>();
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet rs = null;
try {
connection = DbUtil.getConnect();
preparedStatement = connection.prepareStatement(sql);
setPreparedStatement(preparedStatement,params);
rs = preparedStatement.executeQuery();
return mapper.mapping(rs);//处理结果集,由实现类自己完成
} catch (Exception e) {
e.printStackTrace();
} finally {
DbUtil.close(rs,preparedStatement,connection);
}
return null;
}
//DML
public static void Manipulation(String sql,Object...params){
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = DbUtil.getConnect();
preparedStatement = connection.prepareStatement(sql);
setPreparedStatement(preparedStatement,params);//设置值
preparedStatement.execute();
} catch (SQLException e) {
e.printStackTrace();
}finally {//释放资源
DbUtil.close(null,preparedStatement,connection);
}
}
//处理preparedStatement,设置值
private static void setPreparedStatement(PreparedStatement preparedStatement,Object... params) throws SQLException {
for(int i = 0; i < params.length;i++){
preparedStatement.setObject(i+1,params[i]);
}
}
}
对于模板类的设计,充分的考虑到可扩展性,如果只是对t_stuent的数据和类student的操作,不做其他任何的设计就已经可以了,但是如果以后面对业务的扩展或者更改,就显得有点力不从心了。其实针对不同的表的返回集,应该有不同的操作,而怎样操作返回集,不是模板类所关心的内容,而根据单一职责原则(Simple Responsibility Pinciple,SRP),解耦合,于是创建了带泛型的一个接口:IRowMapper
/**
* @Classname IRowMapper
* @Description TODO
* @Date 2020/3/26 13:51
* @Created by ASUS
*/
public interface IRowMapper<E> {
/**
*功能描述:根据结果集的类型进行对应处理
*@param resultSet 待处理的结果集
*Author Jason
*Date 2020-03-26 13:52
*/
E mapping(ResultSet resultSet)throws Exception;
}
对从数据库中查询得到的返回集ResultSet由其具体的实现类完成,这样一来,程序就不止是能够查询t_student的数据,还能查如t__teacher表中的数据,扩展性得到提高。
好了,到了这里,这部分的代码重构应该就差不多了。
下面看看优化后的代码:
优化后的代码
/**
* @Classname stuServicee_2
* @Description TODO
* @Date 2020/3/26 9:35
* @Created by ASUS
*/
public class stuService_2 implements IService {
//各种在SQL语句存储在config/SQL.properties文件中
private static final String INSERT = "INSERT";
private static final String UPDATE = "UPDATE";
private static final String DELETE = "DELETE";
private static final String QUERY_AGE = "QUERY_AGE";
private static final String QUERY_ALL = "QUERY_ALL";
@Override
public void save_student(student stu) {
String sql = DbUtil.getSQL(INSERT);
JDBCtemplate.Manipulation(sql,stu.getStu_Name(),stu.getStu_Age());
}
@Override
public void delete_stu_byId(int id) {
String sql = DbUtil.getSQL(DELETE);
JDBCtemplate.Manipulation(sql,id);
}
@Override
public void update_stu(student stu) {
String sql = DbUtil.getSQL(UPDATE);
JDBCtemplate.Manipulation(sql,stu.getStu_Name(),stu.getStu_Age());
}
//根据年龄(当前认为的主键,实际数据库中没有设置主键)查询
public student Query_stu(int age){
String sql = DbUtil.getSQL(QUERY_AGE);
List<student> students = JDBCtemplate.Query(sql, new StudentRowMapper(),age);
assert students != null;
return students.size() > 0 ? students.get(0) : null;
}
//以List集合的形式,返回所有表中的数据
public List<student> Query_stu(){
String sql = DbUtil.getSQL(QUERY_ALL);
return JDBCtemplate.Query(sql, new StudentRowMapper());
}
static class StudentRowMapper implements IRowMapper<List<student>>{
/**
* 功能描述:处理学生结果集数据
* @param resultSet 待处理的结果集
* Author Jason
*/
@Override
public List<student> mapping(ResultSet resultSet) throws Exception {
List<student> students = new ArrayList<>();
while (resultSet.next()){
students.add(new student(resultSet.getInt("age"),resultSet.getString("name")));
}
return students;
}
}
}
与之前的代码相比,不仅是代码量得到很大程度的精简,更重要的是代码的可读性提高,可扩展性得到升,后期的维护也变得更加方便。
在这里我只是简单地进行了一小部分的代码的重构,以后随着功能的复杂程度不断的加大,程序之间的关系就会变得十分的复杂,而重构就可以将海量的代码按照逻辑有效的设计与实现,在项目的后期维护和扩展时都有很重要的作用。
最后
以上就是迷路黄蜂为你收集整理的代码的重构实例(Java JDBC)学习代码的重构的全部内容,希望文章能够帮你解决代码的重构实例(Java JDBC)学习代码的重构所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复