浅谈Java常见设计模式(六)
对这个世界如果你有太多的抱怨
跌倒了就不敢继续往前走
为什么人要这么的脆弱堕落
请你打开电视看看
多少人为生命在努力勇敢的走下去
我们是不是该知足
珍惜一切就算没有拥有
很久没有回顾过jdbc操作了,今天回顾下原生Java操作数据库的流程,
1、首先载入数据库驱动
2、获取数据库连接
3、获取PreparedStatement
4、执行sql语句
5、处理结果集
6、关闭连接和PreparedStatement、ResultSet
那么对于查询的话,通常来说,1.2.3.4这几个步奏是肯定一致的,第5步,结果集处理,每个查询的结果集处理可能是太一样的,所以是一个可微调的因素。
那么基于此,来尝试进行一次原生的jdbc操作,平常ORM框架用多了,这里来熟悉一些底层一点的东西。
下面例子使用的数据库是Mysql8.0.11
首先有一个实体类,用于一个查询结果的封装:

这里仅列举了几个字段,意思一下就好
下面定义一个RowMapper结果集映射器接口,拥有一个结果封装的方法
package com.lgli.behavior.template.jdbc;import java.sql.ResultSet;/*** 结果集处理接口* @author lgli*/public interface RowMapper<T> {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、获取PreparedStatementPreparedStatement preparedStatement = getPreparedStatement(sql,connection);//4、执行sql语句ResultSet resultSet = preparedStatement.executeQuery();//5、处理结果集List<T> result = dealResult(resultSet,rowMapper);//6、关闭连接和PreparedStatementclose(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());}}}
测试类直接调用查询方法,打印结果:

上面主要类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);}}}
这里一个简单的测试类,输出结果:

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

这里就比较粗超的描述了适配器和方法的基本关系,其实也就是一个简单的适配器的实现,下面描述一个标准一些的场景:
假定有一个播放器及其播放器实现,这个播放器只能播放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{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{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");}}}
写个测试类:

看下类图:

这里的播放器适配器,将2个毫无关系的方法实现关联起来了,这也是简单的适配器实现。
点击下面公众号关注获取更多。。。
欢迎关注点赞转发,谢谢
