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 的功能得以实现。