1. 说明
有些时候我们需要一个管理类,类似 xxxManager 来处理共享的基础数据,它要在项目启动时就进行初始化且查询数据库,而且查询语句不复杂,写一整套的Service或者使用MyBatis的查询API有点儿繁琐,此时写一个简单的Mapper接口就优雅许多。
2. 代码实现
为了简洁,删掉部分不必要的备注,下边是Mapper接口:
/** * 集中管理非单表操作的SQL */ @Mapper public interface ComplexSqlMapper { // 查询 view_manager 表全部数据 @Select(" select * from view_manager ") List<ViewEntity> getViewManagerAllList(); }
下面是管理类,这里没有使用Doc注释:
/** * 查询并处理 view_manager 表数据(为了简洁这里是示例代码 真正的处理要比这个复杂) */ @Component public class ViewManager { // Mapper 注入 @Resource private ComplexSqlMapper complexSqlMapper; // 处理结果保存 private Map<String, String> views = new HashMap(); // 初始化时查询 view_manager 表数据 private ViewManager() { List<ViewEntity> viewEntityList= complexSqlMapper.getViewManagerAllList(); // 处理数据 viewEntityList.forEach(ve-> { views.put(ve.getId(), ve.getName()); }); } }
3. 原因解析
项目启动时有NPE,Debug才发现 complexSqlMapper 是null。我首先想到的是 Mapper 文件没有被检测到,然后确认了启动类里的 org.mybatis.spring.annotation.MapperScan
信息:
@SpringBootApplication @MapperScan(basePackages = "com.example.demo.**.mapper") public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
并没有写错路径,那我们仔细回看 ViewManager 类就会发现 complexSqlMapper 是当作类的成员变量被注入的,那也就是说需要先实例化 ViewManager 类然后再注入 complexSqlMapper,可是我们在类的构造器里就调用了 complexSqlMapper 此时构造器没有执行完,ViewManager 也就没被实例化,那么 complexSqlMapper 自然是个null。至此通过构造器使用 Mapper 查询数据的计划落空了。
4. 问题解决
那就没有方法了吗 ❓ 怎么可能 ❗️ Spring早就想到了你想到的 😏 使用 @PostConstruct
注解的方法将会在依赖注入完成后被自动调用。
改造后的代码【这里只贴出核心代码 无关备注也省略了】
@Component public class ViewManager { @Resource private ComplexSqlMapper complexSqlMapper; private Map<String, String> views = new HashMap(); @PostConstruct private void init() { List<ViewEntity> viewEntityList= complexSqlMapper.getViewManagerAllList(); viewEntityList.forEach(ve-> { views.put(ve.getId(), ve.getName()); }); } }
改造后的代码,项目启动时由于 @Component 注解,ViewManager 类会被实例化并交给容器管理,此时使用的是 ViewManager 类的无参构造器。而 @PostConstruct 注解的 init 方法会待 complexSqlMapper 注入成功后调用。至此,xxxManager 的功能得以实现。