浅谈Java常见设计模式(六)

lgli

共 7854字,需浏览 16分钟

 ·

2021-05-19 13:28

对这个世界如果你有太多的抱怨

跌倒了就不敢继续往前走

为什么人要这么的脆弱堕落

请你打开电视看看

多少人为生命在努力勇敢的走下去

我们是不是该知足

珍惜一切就算没有拥有



很久没有回顾过jdbc操作了,今天回顾下原生Java操作数据库的流程,


1、首先载入数据库驱动


2、获取数据库连接


3、获取PreparedStatement


4、执行sql语句


5、处理结果集


6、关闭连接和PreparedStatement、ResultSet


那么对于查询的话,通常来说,1.2.3.4这几个步奏是肯定一致的,第5步,结果集处理,每个查询的结果集处理可能是太一样的,所以是一个可微调的因素。


那么基于此,来尝试进行一次原生的jdbc操作,平常ORM框架用多了,这里来熟悉一些底层一点的东西。


下面例子使用的数据库是Mysql8.0.11


首先有一个实体类,用于一个查询结果的封装:


29f19690ae9a3942ecb2dcc134429dff.webp


这里仅列举了几个字段,意思一下就好



下面定义一个RowMapper结果集映射器接口,拥有一个结果封装的方法


package com.lgli.behavior.template.jdbc;import java.sql.ResultSet;/** * 结果集处理接口 * @author lgli */public interface RowMapper<T{    mapRow(ResultSet resultSet) throws Exception;}


下面定义一个jdbc操作类,因为以上几个操作步骤,除了某些细节有些许变化之外,大体上都是一致的,那么这里设计了一个类,具有操作数据库的标准步骤方法,同时,通过结果集处理接口RowMapper的具体实现类来实现对细节的微调。那么这个类,简单称为JdbcTemplate类:


package com.lgli.behavior.template.jdbc;
import java.sql.*;import java.util.ArrayList;import java.util.List;
/** * jdbc操作模板 * @author lgli */public abstract class JdbcTemplate {
/** * mysql数据驱动 */ private static final String DATA_SOURCE_DRIVER = "com.mysql.cj.jdbc.Driver";
/** * 数据库连接地址 */ private static final String DATA_SOURCE_URL = "jdbc:mysql://localhost:3306/xttl?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT&useSSL=false";
/** * 用户名 */ private static final String USER = "root";
/** * 密码 */ private static final String PASSWORD = "soft01";


public <T> List<T> executeQuery(String sql,RowMapper<T> rowMapper){ try{ //1、载入数据库驱动 initDataSourceDriver(); //2、获取数据库连接 Connection connection = getConnection(); //3、获取PreparedStatement PreparedStatement preparedStatement = getPreparedStatement(sql,connection); //4、执行sql语句 ResultSet resultSet = preparedStatement.executeQuery(); //5、处理结果集 List<T> result = dealResult(resultSet,rowMapper); //6、关闭连接和PreparedStatement close(connection,preparedStatement,resultSet); return result; }catch (Exception e){ e.printStackTrace(); }
return null; }
protected <T> List<T> dealResult(ResultSet resultSet, RowMapper<T> rowMapper) throws Exception{ List<T> result = new ArrayList<>(); while(resultSet.next()){ result.add(rowMapper.mapRow(resultSet)); } return result; }
protected void close(Connection connection, PreparedStatement preparedStatement,ResultSet resultSet) throws Exception{ if(connection != null){ connection.close(); } if(preparedStatement != null){ preparedStatement.close(); }
if(resultSet != null){ resultSet.close(); } }
protected PreparedStatement getPreparedStatement(String sql,Connection connection) throws Exception { return connection.prepareStatement(sql); }
private Connection getConnection() throws Exception{ return DriverManager.getConnection(DATA_SOURCE_URL,USER,PASSWORD); }
private void initDataSourceDriver() throws Exception{ this.getClass().getClassLoader().loadClass(DATA_SOURCE_DRIVER);    }}


这里我们简单用一个查询举例,其他的数据库操作雷同


可以看到,标准的步骤都是一样的,每个数据库操作都是这么来的,唯一不一样的,就是结果集的处理。


下面定义一个Dao,继承自这个JdbcTemplate类:


package com.lgli.behavior.template.jdbc;
import java.util.List;
/** * dao * @author lgli */public class Dao extends JdbcTemplate{
public List<User> selectUser(){ String sql = "select * from user_basic_info"; return super.executeQuery(sql, resultSet -> { User user = new User(); user.setUserName(resultSet.getString("USER_NAME")); user.setUserUniqueSign(resultSet.getString("USER_UNIQUE_SIGN")); user.setUserPassword(resultSet.getString("USER_PASSWORD")); return user; });    }}


这里定义一个查询方法,调用模板类的executeQuery方法,传入一个RowMapper的实现类,这里即是对查询结果的封装,每一个查询的封装可能存在差异,比如这里是查询user_basic_info,假设查询另外的表,其结果封装肯定就是不一样的了,这里主要利用钩子方法实现对结果的自定义封装。



然后是我们的测试类:


package com.lgli.behavior.template.jdbc;import java.util.List;
/** * test * @author lgli */public class DaoTest {
public static void main(String[] args) { Dao dao = new Dao(); List<User> users = dao.selectUser(); for (User user : users) { System.out.println(user.toString()); } }}


测试类直接调用查询方法,打印结果:


28faade761809c7d7d517b34ecebc28f.webp


上面主要类JdbcTemplate类,我们称为模板类,这种设计模式,称为模板模式,即:一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行。


假定下面这个业务场景:在系统已经存在上面例子中的Dao类,这个

selectUser也已经成熟运行了很久了,

与此同时,系统中有一个对人员处理的方法,


package com.lgli.construct.adapter;import java.util.List;
/** * 人员处理类型 * @author lgli */public class OtherUserDeal { private List<OtherUser> dealUser(List<OtherUser> users){ for(OtherUser user : users){ user.setPassword("******"); } return users;    }}


这个处理类,是专门对OtherUser进行的处理,这个地方的这个OtherUser和前面的User类是不一样的对象。


这个时候,新的需求来了,需要利用OtherUserDeal 对上面例子中的查询结果进行处理。



此时,是可以单独写个方法来进行操作的,那么如果在两个方法业务逻辑很复杂的情况下,显然,从头开始来捋代码逻辑时一个不明智的选择,同时可能出现其他的BUG。


这个时候,可以选择新加入一个适配器类,让这个适配器可以将这两个毫无相关的方法(接口)建立一个桥梁,来解决这个问题。


这里新建一个Adapter类:


package com.lgli.construct.adapter;
import com.lgli.behavior.template.jdbc.Dao;import com.lgli.behavior.template.jdbc.User;
import java.util.ArrayList;import java.util.List;

/** * 适配器类 * @author lgli */public class Adapter extends Dao {
private OtherUserDeal userDeal;
public Adapter(OtherUserDeal userDeal) { this.userDeal = userDeal; }
public List<OtherUser> dealWithUser(){ List<User> users = super.selectUser(); List<OtherUser> otherUsers = new ArrayList<>(); for(User user : users){ OtherUser otherUser = new OtherUser(); otherUser.setPassword(user.getUserPassword()); otherUser.setName(user.getUserName()); otherUser.setUserAccount(user.getUserUniqueSign()); otherUsers.add(otherUser); } return userDeal.dealUser(otherUsers);    }}


一般来说,在实际的工作中,还是有一个比较标准的适配器模式,这里仅仅做个演示说明,其代码结构存在较多的可以改造的地方,就不在这儿优化了。


package com.lgli.construct.adapter;import java.util.List;

/** * 适配器测试类 * @author lgli */public class AdapterTest { public static void main(String[] args) { Adapter adaper = new Adapter(new OtherUserDeal()); List<OtherUser> otherUsers = adaper.dealWithUser(); for(OtherUser user : otherUsers){ System.out.println(user); } }}


这里一个简单的测试类,输出结果:


86c701628f1ac7d813e734a5a738d70f.webp



这里,我们的适配器类,将两个毫无相干的方法,结合起来了:


看下类图:


ce830d6eaa567cdd7f7ad66234c80406.webp


这里就比较粗超的描述了适配器和方法的基本关系,其实也就是一个简单的适配器的实现,下面描述一个标准一些的场景:



假定有一个播放器及其播放器实现,这个播放器只能播放Mp3的音乐:


MediaPlay:


package com.lgli.construct.adapter;/** * 媒体播放器接口 * @author lgli */public interface MediaPlay {    void play();}


播放器实现MediaPlayImpl,只能播放MP3:


package com.lgli.construct.adapter;/** * 媒体播放器实现 * @author lgli */public class MediaPlayImpl implements MediaPlay{    @Override    public void play() {        System.out.println("播放MP3!!!");    }}


这个时候,忽然项目引进了一个高级一点的播放器接口和实现:


高级播放器接口AdvancedMediaPlay


package com.lgli.construct.adapter;/** * 高级播放器 * @author lgli */public interface AdvancedMediaPlay {
void play();}


高级播放器接口的其中一个实现AdvancedMediaPlayImpl,可以播放MP4文件:


package com.lgli.construct.adapter;
/** * 高级播放器实现 * @author lgli */public class AdvancedMediaPlayImpl implements AdvancedMediaPlay{ @Override public void play() { System.out.println("播放MP4!!!"); }}


这时候,有一个需求,需要用只能播放MP3的MediaPlayImpl去播放MP4,怎么办?此时最好的解决方案就是,写一个适配器,来关联这两个实现:



package com.lgli.construct.adapter;

/** * 播放器适配器 * @author lgli */public class MediaPlayAdapter {    public void play(String type){ if(type.contains(".Mp3")){ new MediaPlayImpl().play(); }else if(type.contains(".Mp4")){ new AdvancedMediaPlayImpl().play(); }else{ System.out.println("can not play media"); }    }}


写个测试类:


e0f8e45570fcdf404513fbd8a1f5d0c8.webp


看下类图:


bb5e39847d652edcb599c21b18350893.webp


这里的播放器适配器,将2个毫无关系的方法实现关联起来了,这也是简单的适配器实现。






点击下面公众号关注获取更多。。。


欢迎关注点赞转发,谢谢

浏览 37
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报