客户有新需求,需要修改数据库、配置文件等,可以通过打补丁的方式来处理:
使用方法:
1、创建一个类继承ApplicationRunner,重写run方法;
2、将这个类丢给容器,写法如下:
@Component
public class ApplicationRunnerImpl implements ApplicationRunner {
    @Override
    public void run(ApplicationArguments args) throws Exception {
    }
}
SpringBoot自动会运行run方法,至于是通过反射查到该对象,还是从容器里拿到对象集在一个个instanceof判断,或者其他方法获取对象,无所谓。只要实现ApplicationRunner,再通过注解注入容器中就行。
例子:数据库某个字段设计时长度设置500,客户使用一段时间后发现不够用了,需要修改成2000,这个时候最快的解决方案可以是现场,也可以远程直接打开数据库修改就好了。可如果数据库已经使用了很长一段时间,里面有机密数据,客户拒接提供数据库,这个时候只能在代码里改,改完发给客户一个新版本就好。要改的话在最简单的方式是在启动类里改,使用补丁是为了统一管理,后期代码好找(维护)。
补丁类:
@Component
public class PatchDataSecond extends PatchBase {
    @Autowired
    EntityManager entityManager;
    
    public PatchDataSecond(EntityManager entityManager) {
        super(entityManager);
    }
   
    @Override
    public boolean run() {
        try {
            String sql = "ALTER TABLE bm_recovery MODIFY COLUMN record  varchar(2000)";
            entityManager.createNativeQuery(sql).executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
}
ApplicationRunner实现类:
/**
 * 在app运行成功后执行一些特定操作,一些特殊的任务。例如初始化参数、数据库初始化等操作
 */
@Component
@Transactional
public class ApplicationRunnerImpl implements ApplicationRunner {

    String TAG = "ApplicationRunnerImpl";
    /**
     * @PersistenceContext 注入的是实体管理器,执行持久化操作。
     * EntityManager 实体管理器
     */
    @PersistenceContext
    private EntityManager entityManager;

    @Autowired
    PatchDataSecond patchDataSecond;

    @Override
    public void run(ApplicationArguments args) throws Exception {
        patchDataSecond.run();
    }
}
如果想方便的话可以在补丁类和ApplicationRunner实现类中间套一层,加一个补丁管理类,补丁管理类里维护一个list集合,每有一个新补丁修改补丁管理类的代码,将新补丁加入list,所有补丁添加结束后,可以for循环拿出来一个个顺序执行补丁的run方法。而ApplicationRunner实现类只要执行补丁管理类的run方法就好。
补丁管理类:
@Component
public class PatchManage {
    final static String TAG = "PatchManage";

    EntityManager entityManager;
    List<PatchBase> patchList = new ArrayList<>();

    @Autowired
    PatchDataSecond patchDataSecond;

    public void init(EntityManager entityManager) {
        this.entityManager = entityManager;
        patchDataSecond.setEntityManager(entityManager);
        //添加补丁
        patchList.add(patchDataSecond);
    }
    /**
    * ApplicationRunner实现类可以把entityManger传递过来
    */
    public void run(EntityManager entityManager) {
        init(entityManager);
        for (PatchBase patch : patchList) {
            patch.run();
        }
    }

}
完整的补丁管理类:
/**
 * 补丁管理类
 * @author java
 */
@Component
public class PatchManage {

    /**
     * 实体管理类
     */
    EntityManager entityManager;

    /**
     * 补丁集合
     */
    List<PatchBase> patchList = new ArrayList<>();

    /**
     * 补丁对象,实现PatchBase接口
     */
    private final PatchInitDbData patchInitDbData;
    private final PatchDataSecond patchDataSecond;

    /**
     * 通过构造方法注入对象,可以避过阿里巴巴的代码规范,代码规范不推荐使用@Autowired注入对象
     */
    public PatchManage(PatchInitDbData patchInitDbData, PatchDataSecond patchDataSecond) {
        this.patchInitDbData = patchInitDbData;
        this.patchDataSecond = patchDataSecond;
    }

    /**
     * 业务管理类的初始化,新补丁在这里加入
     * @param entityManager 实体管理对象,找SpringBoot要
     */
    public void init(EntityManager entityManager) {
        //将实体管理对象给到补丁,如果用不上也可以不给
        this.entityManager = entityManager;
        patchInitDbData.setEntityManager(entityManager);
        patchDataSecond.setEntityManager(entityManager);
        //加入list集合
        patchList.add(patchInitDbData);
        patchList.add(patchDataSecond);
    }

    /**
     * 补丁执行类Unchecked assignment: 'java.util.List' to 'java.util.List<com.brain.machine.entity.PatchVersion>'
     */
    public void run() {
        //在数据库里加一张补丁管理表,管理补丁,已经执行过的补丁不再执行,这在设计之初应该设计进去
        String sql = "select * from patch_version";
        Query query = entityManager.createNativeQuery(sql, PatchVersion.class);
        List<PatchVersion> versions = query.getResultList();

        //循环遍历补丁
        for (PatchBase patch : patchList) {
            //从数据库获取补丁的运行态
            String status = getPatchStatus(versions, patch.name());
            //如果为null,代表这是新加的补丁,写入数据库
            if ("null".equals(status)) {
                insertNewPatch(patch);
            }
            //如果是end或者run,代表执行过的补丁,结束当前循环(end是在补丁结束的时候打上的,这个run在哪打上的没找到)
            if ("end".equals(status) || "run".equals(status)) {
                continue;
            }
            //新补丁执行run方法,如果执行成功,在数据库里留下痕迹,告知已经执行过了
            if (patch.run()) {
                endPatch(patch);
            }
        }
    }

    /**
     * 获取补丁状态,versions是从数据库查到的数据,检查补丁的运行状态,查不到就返回null
     */
    public String getPatchStatus(List<PatchVersion> versions, String name) {
        //循环遍历
        for (PatchVersion version : versions) {
            //名称相同代表同一个补丁,返回运行状态(这里好像没做补丁同名处理,数据库也没有...)
            if (version.name.equals(name)) {
                return version.status;
            }
        }
        return "null";
    }

    /**
     * 添加补丁,如果补丁第一次执行,在数据库里记录下来
     */
    public void insertNewPatch(PatchBase patch) {
        //加入数据库
        String newSql = "insert into patch_version(name, status, create_time) values(?, ?, ?)";
        Query query = entityManager.createNativeQuery(newSql)
                .setParameter(1, patch.name())
                //ready准备状态
                .setParameter(2, "ready")
                .setParameter(3, new Date());
        query.executeUpdate();
    }


    /**
     * 补丁执行结束后,修改自己的状态。这样下一次启动项目,会跳过该补丁(注意:补丁执行一次就不会再执行,如果写错了,要么改名,要么重写一个类)
     * 禁用
     */
    public void endPatch(PatchBase patch) {
        String updateSql = "update patch_version set status = 'end', end_time = ? where name = ? ";
        Query query = entityManager.createNativeQuery(updateSql)
                .setParameter(1, new Date())
                .setParameter(2, patch.name());
        query.executeUpdate();
    }
}

 

posted on 2022-04-27 10:57  时寒很苦恼  阅读(408)  评论(0)    收藏  举报