需求:前端会传来一些图片数据,比如图片名称,图片长宽、大小等。后端需要根据实际情况存入mysql、oracle、clickhouse等不同的数据库。
上面的需求是一个非常好的使用策略模式实现的例子。
Mapper层
定义一个顶级接口,主要定义操作图片数据的方法,这里只写了一个图片插入的方法。
/**
* 图片操作mapper
*
*/
public interface ImageMapper {
/**
* 返回数据库类型
*
* @return 数据库类型
*/
String databaseType();
/**
* 图片插入方法
*
* @param image 图片对象
*/
void insertImageRecord(ImageBean image);
}
定义不同的操作数据库的mapper,下面定义一个操作clickhouse的mapper,注意定义对应的mapper.xml文件,这里不体现。
@Mapper
public interface ClickhouseImageMapper extends ImageMapper {
/**
* 返回数据库类型
* @return 数据库类型
*/
@Override
default String databaseType(){
return "clickhouse";
}
/**
* 向clickhouse数据库的iot_image表中插入数据
*
* @param image 图片信息
*/
@Override
void insertImageRecord(ImageBean image);
}
定义操作mysql的mapper,注意定义对应的mapper.xml文件,这里不体现。
@Mapper
public interface MySqlImageMapper extends ImageMapper {
/**
* 返回数据库类型
* @return 数据库类型
*/
@Override
default String databaseType(){
return "mysql";
}
/**
* 向clickhouse数据库的iot_image表中插入数据
*
* @param image 图片信息
*/
@Override
void insertImageRecord(ImageBean image);
}
Service层
定义context对象,可以理解为一个service对象。这个对象主要的作用是将实现了ImageMapper.class
的实例对象存储到一个map中,当有人要调用这个对象的insertImageRecord
方法时,从map中取出对应mapper执行数据库的插入操作。
@Service
public class ImageContext implements ApplicationContextAware {
private final Map<String, ImageMapper> imageMapperMap = new ConcurrentHashMap<>();
public void insertImageRecord(String dbType, ImageBean image) {
final ImageMapper imageMapper = Optional.ofNullable(imageMapperMap.get(dbType))
.orElseThrow(() -> new IllegalArgumentException("invalid database type: " + dbType));
imageMapper.insertImageRecord(image);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
final Map<String, ImageMapper> map = applicationContext.getBeansOfType(ImageMapper.class);
map.values().forEach(source -> imageMapperMap.put(source.databaseType(), source));
}
}
在需要的地方调用上述方法。
@Service
public class GetAsyncAiResultService {
/**
* 历史库类型,默认clickhouse
*/
@Value("${image.history.database.type:clickhouse}")
private String databaseType;
//注入上下文对象
@Resource
ImageContext imageContext;
/**
* 组织图片信息插入到clickhouse中
*
* @param imageName 图片名称
* @param imageResult 图片结果
*/
void insertImageRecord(String imageName, JSONObject imageResult, JSONObject imageMetaData) {
//省略处理逻辑
//...
//插入需要的数据库,数据库类型可以通过配置文件注入的方式,这里为了展示明显,写死了
imageContext.insertImageRecord(databaseType, image);
}
}
总结
使用策略模式以后代码非常符合开闭原则。比如现在需要将数据插入到Oracle中,那么上述提到的所有文件都不需要动,只需要再写一个OracleImageMapper继承ImageMapper,在需要将数据插入Oracle的时候,修改配置文件即可完成。
但是,使用策略模式需要提前设计好再开发,比如本文我们在ImageMapper文件中只定义了一个插入数据库的方法,后续开发过程中发现,不仅需要插入数据库还需要查询数据库,那么上述所有提到的文件都需要变更。