浅谈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、获取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());
}
}
}
测试类直接调用查询方法,打印结果:
上面主要类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个毫无关系的方法实现关联起来了,这也是简单的适配器实现。
点击下面公众号关注获取更多。。。
欢迎关注点赞转发,谢谢