需求:前端会传来一些图片数据,比如图片名称,图片长宽、大小等。后端需要根据实际情况存入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 数据库类型*/@Overridedefault String databaseType(){return "clickhouse";}/*** 向clickhouse数据库的iot_image表中插入数据** @param image 图片信息*/@Overridevoid insertImageRecord(ImageBean image);
}
定义操作mysql的mapper,注意定义对应的mapper.xml文件,这里不体现。
@Mapper
public interface MySqlImageMapper extends ImageMapper {/*** 返回数据库类型* @return 数据库类型*/@Overridedefault String databaseType(){return "mysql";}/*** 向clickhouse数据库的iot_image表中插入数据** @param image 图片信息*/@Overridevoid 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);}@Overridepublic 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;//注入上下文对象@ResourceImageContext imageContext; /*** 组织图片信息插入到clickhouse中** @param imageName 图片名称* @param imageResult 图片结果*/void insertImageRecord(String imageName, JSONObject imageResult, JSONObject imageMetaData) {//省略处理逻辑//...//插入需要的数据库,数据库类型可以通过配置文件注入的方式,这里为了展示明显,写死了imageContext.insertImageRecord(databaseType, image); }
}
总结
使用策略模式以后代码非常符合开闭原则。比如现在需要将数据插入到Oracle中,那么上述提到的所有文件都不需要动,只需要再写一个OracleImageMapper继承ImageMapper,在需要将数据插入Oracle的时候,修改配置文件即可完成。
但是,使用策略模式需要提前设计好再开发,比如本文我们在ImageMapper文件中只定义了一个插入数据库的方法,后续开发过程中发现,不仅需要插入数据库还需要查询数据库,那么上述所有提到的文件都需要变更。