精华内容
下载资源
问答
  • 它将Azure WebJob转变为动态的MessageBus,它遵循“配置公约”和开放/封闭原则。 本质上,此代码演示了如何根据可用消息自动向WebJob注册新的QueueTriggers 。 通过使用此解决方案,在新队列上侦听新消息就像在新...
  • Qrartz学习---基于springboot从入门到实现对于触发器定时任务的动态增删改查; 一、 quartz: 在学习quartz之前,我们先来认识一下quartz,它到底是个什么东西,我们为什么要学它; 看一下官方的解释: 再来看...

    Qrartz学习---基于springboot从入门到实现对于触发器定时任务的动态增删改查;

    一、 quartz:

       在学习quartz之前,我们先来认识一下quartz,它到底是个什么东西,我们为什么要学它;

    • 看一下官方的解释:

    • 再来看一些比较通俗一点的解释:

    说的比较通俗一点,quartz就是一个定时器,用来完成复杂一点的定时任务的,它的使用也是非常的简单,下边我们将由浅入深的介绍quartz这个框架;

    二、quartz的一些使用属性和概念的介绍:

         要了解一个框架之前,我们首先要知道它里边最基本的一些类的属性是什么:

    说的比较通俗一点:

    1. job:就是你的定时器需要完成的任务是什么;
    2. jobDetail:有的时候,我们的job定义的任务可能会比较的抽象,这个时候jobDetail就发挥作用了,它是你的一个job的实例。job和jobDetail是不分家的。
    3. trigger:触发器 ,他的作用是什么时候来触发这个定时任务,比如说每三天执行一次,每周末执行一次等等,触发器一共分两种,分别是简单的触发器和cron触发器;
    4. scheduler:调度器,它的作用就是将trigger和job联系起来,因为不管怎么说,触发器还是要作用在job身上才是有用的嘛;

    他们之间的关系如下图所示:

    简单点的关系:

    复杂点的关系:

    从上图我们可以看出,触发器可以绑定job(也可以用scheduler绑定),而scheduler和绑定触发器,进而将三者绑定在一起,同时呢,一个job是可以对应多个jobDetail的;

    而一个jobDetail也是可以对应多个trigger的;

    关于job和jobDetail的关系:

    好的,概念扫盲暂时到这里,接下来让我们来看一点简单的实例把!

    三、简单的quartz定时任务的使用:

    前置pom:

     <!-- quartz -->
            <dependency>
                <groupId>org.quartz-scheduler</groupId>
                <artifactId>quartz</artifactId>
                <version>2.3.2</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context-support</artifactId>
            </dependency>

    3.1、spring的注解@Bean方式:

    
        @Bean
        public JobDetailFactoryBean jobDetailFactoryBean() {
            JobDetailFactoryBean factoryBean = new JobDetailFactoryBean();
            // 关联我们自己的job类
            factoryBean.setJobClass(QuartzDemo.class);// 这里他将job对象创建仅仅是反射,没有加入到spring容器里边
            return factoryBean;
        }
        
        @Bean
        public CronTriggerFactoryBean cronTriggerFactoryBean(JobDetailFactoryBean jobDetailFactoryBean) {
            CronTriggerFactoryBean factoryBean = new CronTriggerFactoryBean();
            String cronExpress = "0/4 * * * * ? *";
            factoryBean.setCronExpression(cronExpress);
            return factoryBean;
        }
    
        /*     * 创建scheduler对象*/
    
        @Bean
        public SchedulerFactoryBean schedulerFactoryBean(CronTriggerFactoryBean CronTriggerFactoryBean, MyAdptableFactory myAdptableFactory) {
            SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
            // 关联trigger
            schedulerFactoryBean.setTriggers(CronTriggerFactoryBean.getObject());
            // 用自己写的jobFactory覆盖本来的factory实现将job加入到spring容器中
            schedulerFactoryBean.setJobFactory(myAdptableFactory);
            return schedulerFactoryBean;
        }

    3.2、为什么要自己重写工厂类覆盖quartz本来的;

    其中这里我们在最后创建scheduler的时候,说到了将自己写的jobFactory覆盖quartz本来的jobFactory,这是为什么呢?

    其实我们的job的创建过程是在AdaptableJobFactory这个类中完成的,我们来看一下这个类的源码:

    public class AdaptableJobFactory implements JobFactory {
        public AdaptableJobFactory() {
        }
    
        public Job newJob(TriggerFiredBundle bundle, Scheduler scheduler) throws SchedulerException {
            try {
                Object jobObject = this.createJobInstance(bundle);
                return this.adaptJob(jobObject);
            } catch (Throwable var4) {
                throw new SchedulerException("Job instantiation failed", var4);
            }
        }
    
        protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
            Class<?> jobClass = bundle.getJobDetail().getJobClass();
            return ReflectionUtils.accessibleConstructor(jobClass, new Class[0]).newInstance();
        }
    
        protected Job adaptJob(Object jobObject) throws Exception {
            if (jobObject instanceof Job) {
                return (Job)jobObject;
            } else if (jobObject instanceof Runnable) {
                return new DelegatingJob((Runnable)jobObject);
            } else {
                throw new IllegalArgumentException("Unable to execute job class [" + jobObject.getClass().getName() + "]: only [org.quartz.Job] and [java.lang.Runnable] supported.");
            }
        }
    }

    它的重点就在createJobInstance 这个方法中,通过这个方法我们可以看到,job的创建仅仅就是一个反射的过程,并不是通过spring的动态代理代理出来的,这样的话就会造成很多问题,比如说:事务的失效,不能在job中用@AutoWire注解来注入别的类使用等等;

    所以我们要自己重写他的一个实例过程,将他加入到spring容器中去;代码如下:

    @Component("MyAdptableFactory")
    public class MyAdptableFactory extends AdaptableJobFactory {
        /**
         * 改方法需要将实例话的任务对象否手动的添加到springIOC容器中并且完成对象的注入
         * 因为quartz的本来的创建job只是用反射创建对象加入这种情况是不能呗spirng容器所管理的
         * @param bundle
         * @return
         * @throws Exception
         */
        //AutowireCapableBeanFactory 可以将一个对象添加到spring IOC容器中,并且完成该对象的注入
        @Autowired
        private AutowireCapableBeanFactory autowireCapableBeanFactory;
        @Override
        protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
            Object object=super.createJobInstance(bundle);
             //将object添加到spring容器中
            this.autowireCapableBeanFactory.autowireBean(object);
            return object;
        }
    }

    这样的话,我们就可以在job中用 @Autowired注解来注入别的bean了,但是需要注意即使这样,我们自己实现的job类,还是无法应用事务的,因为这是我们手动将自己创建出来的类;

    3.3、原生的写法实现简单定时任务:

    在这个里边我们实现了将一个job里边绑定多个触发器;

    这里对其中某些方法做一些解释:

    其中withIdentity(绑定身份):作用是对于你创建的触发器或者job进行命名分组,作用是好管理;

    usingJobData:作用是给job或者trigger一个属性值,这个属性值你可以在job中进行调用;

    withSchedule:作用是给触发器一个触发事件,有两种,分别是简单的和cron的;

    getJobDataMap:也是给job或者trigger进行注入属性;

    forJob:给触发器绑定一个job;

    //创建一个scheduler
            Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
            scheduler.getContext().put("skey", "svalue");
    
            //创建一个Trigger
            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("trigger1", "group1") //进行触发器的命名分组
                    .usingJobData("t1", "第一个触发器")  // 注入属性
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(10)
                            .repeatForever()).build(); // 进行时间间隔的设置
            trigger.getJobDataMap().put("t2", "tv2");
    
    
            Trigger trigger1=TriggerBuilder.newTrigger()
                             .withIdentity("trigger2","group2")
                             .forJob("myjob","mygroup")
                             .usingJobData("t3","第二个触发")
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3)
                            .repeatForever()).build();
            Trigger trigger2=TriggerBuilder.newTrigger()
                    .withIdentity("trigger2","group3")
                    .forJob("myjob","mygroup")
                    .usingJobData("t3","第二次触发修改")
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(2).repeatForever()).build();
    
    
            //创建一个job
            JobDetail job = JobBuilder.newJob(jobDemo1.class)
                    .usingJobData("j1", "jv1") // 注入属性
                    .withIdentity("myjob", "mygroup").build();//进行job的命名分组
            job.getJobDataMap().put("j2", "jv2");// 注入属性
    
            //注册trigger并启动scheduler
            scheduler.scheduleJob(job,trigger);
            scheduler.scheduleJob( trigger1);
            scheduler.scheduleJob(trigger2);
            scheduler.start();

    3.3.1、上述代码中,jobDetail和trigger都创建好了,呢么job如何创建呢?我们看下边的代码---(只演示原生写法的job)

    从代码中可以看出,我们可以把创建jobDetail和trigger时注入的属性一一拿出来,其中呢,getMergedJobDataMap是综合了jobDetail和trigger的属性并集,并且优先选取trigger中的属性;

    public class jobDemo1 implements Job {
        public jobDemo1() {
        }
    
        @Override
        public void execute(JobExecutionContext context) throws JobExecutionException {
    //        Object tv1 = context.getTrigger().getJobDataMap().get("t1");
    //        Object tv2 = context.getTrigger().getJobDataMap().get("t2");
    //        Object jv1 = context.getJobDetail().getJobDataMap().get("j1");
    //        Object jv2 = context.getJobDetail().getJobDataMap().get("j2");
    //        Object sv = null;
    //        try {
    //            sv = context.getScheduler().getContext().get("skey");
    //        } catch (SchedulerException e) {
    //            e.printStackTrace();
    //        }
    //        System.out.println(tv1+":"+tv2);
    //        System.out.println(jv1+":"+jv2);
    //        System.out.println(sv);
    //        System.out.println("hello:"+ LocalDateTime.now());
            JobKey key = context.getJobDetail().getKey();//获取jobDetail的分组和命名
            TriggerKey key1 = context.getTrigger().getKey();
            JobDataMap dataMap=context.getMergedJobDataMap();
            String j1 = dataMap.getString("j1");
            System.out.println("key = " + key);
            System.out.println("key1 = " + key1);
            System.out.println("dataMap = " + dataMap.get("t3"));
            System.out.println("j1 = " + j1);
    
    
        }
        }
    

    恭喜你,如果你看到这里的话,呢么你对quartz的基本操作已经了解的差不多了,接下来的篇幅,就让我们去了解进阶的篇幅把!

    四、略微复杂的quartz的使用:动态添加修改定时任务

         设想一个项目场景:

    在一个项目中,我需要动态的向项目中添加或者修改定时任务,还有暂停和启动的操作,相当于动态的添加定时任务,如果有这种需求 的话呢?你打算怎么做?

    4.1、quartz的固化(本案例不使用,仅作为学习)

     这样的话,我们就需要将定时任务集成到数据库中了,在这方面,quartz给我们做了固化,具体的操作很简单:

    在项目的配置文件(application.yml)中加入如下配置;

    其中,always,代表每次启动项目都会给数据库添加quartz的表,它里边还有另外两个属性:never、embedded

    spring:
      datasource:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/test?serverTimezone=UTC
        username: root
        password: 123456
      quartz:
        job-store-type: jdbc
        jdbc:
          initialize-schema: always

    加入之后,我们再启动项目的时候,会看到:日志中会有这么几行字:具体的意思就是,现在的quartz是以本地持久化的形式来的,不是在内存中的,并且没有集群;

    现在的话,你的数据库中,就会多了几张表:分别存储着不同的信息;

    4.2、自己创建表进行jobDetail和trigger的动态添加修改:

    先给大家贴出一个实体 SettleTaskPlan ,后续说明作用:

    public class SettleTaskPlan extends EntityBase implements Serializable {    
    private static final long serialVersionUID = 1L;
    	@TableId(value="SETTLE_TASK_ID", type = IdType.INPUT)
    	@ExcelProperty(value = "序号",index = 0)
    	private java.lang.Long settleTaskId;
    	@TableField("APP_CODE")
    	@ExcelProperty(value = "应用编码",index = 19)
    	private java.lang.String appCode;
    	@TableField("CHECK_REMARK")
    	@ExcelProperty(value = "审核意见",index = 13)
    	private java.lang.String checkRemark;
    	@TableField("CHECK_STATE")
    	@ExcelProperty(value = "审核状态",index = 14)
    	private java.lang.String checkState;
    
    	@TableField("CHECK_TIME")
    	@ExcelProperty(value = "审核时间",index = 15)
    	@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    	private java.util.Date checkTime;
    	@TableField("CHECK_USER_ID")
    	@ExcelProperty(value = "审核人id",index = 12)
    	private java.lang.Long checkUserId;
    	@TableField("CRON_EXPRESS")
    	@ExcelProperty(value = "执行表达式",index = 3)
    	private java.lang.String cronExpress;
    	@TableField("DEL_FLAG")
    	@ExcelProperty(value = "删除标记",index = 16)
    	private java.lang.String delFlag;
    	@TableField("DISABLE_FLAG")
    	@ExcelProperty(value = "禁用标记",index = 17)
    	private java.lang.String disableFlag;
    	@TableField("EXEC_TIMES")
    	@ExcelProperty(value = "执行次数",index = 9)
    	private java.lang.Long execTimes;
    	@TableField("EXECUTE_FUN")
    	private java.lang.String executeFun;
    	@TableField("EXECUTE_JSON_PARAM")
    	private java.lang.String executeJsonParam;
    	@TableField("FAILED_TIMES")
    	@ExcelProperty(value = "失败次数",index = 10)
    	private java.lang.Long failedTimes;
    	@TableField("LAST_EXECUTED")
    	@ExcelProperty(value = "最后一次执行时间",index = 7)
    	@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    	private java.util.Date lastExecuted;
    	@TableField("LAST_STATE")
    	@ExcelProperty(value = "最后一次执行状态",index = 8)
    	private java.lang.String lastState;
    	@TableField("REMARK")
    	@ExcelProperty(value = "备注",index = 18)
    	private java.lang.String remark;
    	@TableField("START_EXECUTED")
    	@ExcelProperty(value = "开始时间",index = 4)
    	@JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone = "GMT+8")
    	private java.util.Date startExecuted;
    	@TableField("SUCCESS_TIMES")
    	@ExcelProperty(value = "成功次数",index = 11)
    	private java.lang.Long successTimes;
    	@TableField("TASK_CODE")
    	@ExcelProperty(value = "计划编码",index = 1)
    	private java.lang.String taskCode;
    	@TableField("TASK_NAME")
    	@ExcelProperty(value = "计划名称",index = 2)
    	private java.lang.String taskName;
    	@TableField("TASK_TYPE")
    	@ExcelProperty(value = "计划类型",index = 5)
    	private java.lang.String taskType;
    	@TableField("TRIGGER_FUN")
    	@ExcelProperty(value = "模块代码",index = 6)
    	private java.lang.String triggerFun;
    
    	@TableField("SETTLE_OBJECT")
    	private String settleObject;

     

     

    前边已经介绍了简单的quartz的应用,相信大家肯定都可以自己手动创建一个定时任务并且跑起来了;

    但是想要完成动态的添加修改,呢么应该怎么做呢?

    我们可以想一下我们的需求,动态的添加修改;我们再看看前边文章的内容,前边的文章,我们介绍了如何在springboot里边添加一个定时任务,呢么是不是我们只需要再知道如何去修改和删除定时任务,呢么我们的需求就完成了呢?

    下边介绍一下如何对程序中的定时任务进行修改和删除:

    定时任务的修改:

     /**
         * 根据核算计划修改定时任务
         *
         * @param settleTaskPlan
         * @return
         * @throws SchedulerException
         */
        public boolean updateQuartzPlan(SettleTaskPlan settleTaskPlan) throws SchedulerException {
            TriggerKey triggerKey = TriggerKey.triggerKey(settleTaskPlan.getSettleTaskId().toString());
            CronTrigger trigger = (CronTrigger) scheduler.getTrigger(triggerKey);
            Trigger trigger1 = trigger.getTriggerBuilder()
                    .withIdentity(triggerKey)
                    .withSchedule(CronScheduleBuilder.cronSchedule(settleTaskPlan.getCronExpress()))
                    .build();
            scheduler.rescheduleJob(triggerKey, trigger1);
            return true;
        }

    定时任务的添加:

      /**
         * 根据核算计划新增一个定时任务详情到quartz中
         *
         * @param settleTaskPlan
         * @return
         * @throws SchedulerException
         */
        public boolean addQuartzPlan(SettleTaskPlan settleTaskPlan) throws SchedulerException {
            SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
            schedulerFactoryBean.setJobFactory(new MyAdptableFactory());
            //创建一个jobDetail
            JobDetail jobDetail = JobBuilder.newJob(QuartzDemo.class)
                    .withIdentity(settleTaskPlan.getSettleTaskId().toString())
                    .build();
    
            //创建对应的触发器
            Trigger trigger = TriggerBuilder.newTrigger()
                    .startNow()
                    .withIdentity(settleTaskPlan.getSettleTaskId().toString())
                    .usingJobData("settleTaskId", settleTaskPlan.getSettleTaskId().toString())
                    .withSchedule(CronScheduleBuilder.cronSchedule(settleTaskPlan.getCronExpress()))
                    .build();
            //将触发器和jobDetail联合起来
            scheduler.scheduleJob(jobDetail, trigger);
            return true;
        }

    定时任务的删除:

     /**
         * 按照核算计划主键将定时计划从quartz中剔除掉
         *
         * @param settleTaskPlans
         * @return
         */
        public boolean deleteQuartzPlan(List<SettleTaskPlan> settleTaskPlans) throws SchedulerException {
            for (SettleTaskPlan settleTaskPlan : settleTaskPlans) {
                //将对应的jobdetail删除掉
                JobKey jobKey = JobKey.jobKey(settleTaskPlan.getSettleTaskId().toString());
                scheduler.deleteJob(jobKey);
            }
            return true;
        }

     

    同时我们应该再去思考一个问题,我们如何将这些定时任务存储起来呢?总不能我添加完在下次启动的时候就没有了吧?这是不合理的。

    所以我们要在数据库建立一张表SettleTaskPlan ,将定时任务存储起来,定时任务的分组命名信息就是表的主键,这样我们就可以根据表的主键创建修改删除对应的定时任务了;

    表中最主要的信息就是两个:标识符(主键),cron表达式,其余的可以根据自己的需要自行添加;

    建立好表之后,我们只需要在系统的每次启动前,循环访问数据库添加对应的定时任务就可以了:

    public class QuartzConfig {
        //TODO:两种思路,一种是给每一个事件都创建一个jobDetail和一个触发器,另一种是给一个jobDetail创建多个触发器
        @Autowired
        SettleTaskPlanService settleTaskPlanService;
    
        @Autowired
        public Scheduler scheduler;
        //在每次启动项目的时候,去循环数据库中的定时任务然后启动这些定时任务
        @PostConstruct
        public void initJob()throws Exception{
            //从数据库中查询所有的定时任务并且一个一个启动
            QueryWrapper<SettleTaskPlan> queryWrapper=new QueryWrapper();
            queryWrapper.eq("DEL_FLAG","0");
            queryWrapper.eq("DISABLE_FLAG","0");
            queryWrapper.eq("CHECK_STATE","1");
            List<SettleTaskPlan> list = settleTaskPlanService.list(queryWrapper);
            //用自己写的jobFactory覆盖本来的factory实现将job加入到spring容器中
                SchedulerFactoryBean schedulerFactoryBean=new SchedulerFactoryBean();
                schedulerFactoryBean.setJobFactory(new MyAdptableFactory());
                for (SettleTaskPlan taskPlan : list) {
                    //创建一个jobDetail
                    JobDetail jobDetail= JobBuilder.newJob(QuartzDemo.class)
                            .withIdentity(taskPlan.getSettleTaskId().toString())
                            .build();
    
                    //创建对应的触发器
                    Trigger trigger=TriggerBuilder.newTrigger()
                            .startNow()
                            .withIdentity(taskPlan.getSettleTaskId().toString())
                            .usingJobData("settleTaskId",taskPlan.getSettleTaskId().toString())
                            .withSchedule(CronScheduleBuilder.cronSchedule(taskPlan.getCronExpress()))
                            .build();
                    //将触发器和jobDetail联合起来
                    scheduler.scheduleJob(jobDetail, trigger);
                    scheduler.start();
                }
        }
    }

    以上就是quartz的动态添加修改;

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    展开全文
  • Android触发器组件BroadcastReceiver详解

    千次阅读 2015-11-03 12:28:07
    动态注册 广播的发送概述BroadcastReceiver是Android四大组件之一,用于监听系统的广播消息。BroadcastReceiver使用非常简单,它在工作方式上更接近于函数,当BroadcastReceiver对象被构造出来后,通常只

    目录


    概述

    BroadcastReceiver是Android四大组件之一,用于监听系统的广播消息。BroadcastReceiver使用非常简单,它在工作方式上更接近于函数,当BroadcastReceiver对象被构造出来后,通常只执行BroadcastReceiver.onReceive方法,便结束了自己的生命周期。虽然使用简单,但是它的功能却非常强大,因为BroadcastReceiver可以监听系统全局广播,所以BroadcastReceiver可以用于不同进程组件之间的通信。

    此外,和所有组件一样,BroadcastReceiver组件对象也是在应用进程的主线程中被构造,因此,其功能函数onReceive的执行必须是同步且快速的,否则就会阻塞与用户交互的当前进程,影响用户体验

    BroadcastReceiver的设计,解决了应用开发中一个很重要的问题,就是后台事件的监听。例如,早期的Symbian中做一个来电归属地提示之类的应用,就必须让应用进程始终运行着,并一直在后台循环等待相关事件的发生。这样容易浪费系统资源,而且很容易被杀死。而有了BroadcastReceiver的Android,只有当事件真正发生时,组件管理服务才会根据配置信息通知对应的触发器组件对象,构造执行组件的进程。


    BroadcastReceiver的使用


    创建BroadcastReceiver的子类

    首先,需要构建自己的BroadcastReceiver子类,继承自android.content.BroadcastReceiver类,并实现其中的onReceive方法。示例代码如下:

    import android.content.BroadcastReceiver;
    import android.content.Context;
    import android.content.Intent;
    
    public class MyBroadcastReceiver extends BroadcastReceiver {
        private static final String MY_ACTION = "system.update";
    
        public MyBroadcastReceiver() {
        }
    
        @Override
        public void onReceive(Context context, Intent intent) {
            if (MY_ACTION.equals(intent.getAction())) {
                // TODO: 如果是需要监听的通知,再去处理相关的业务逻辑
            }
        }
    }

    注册BroadcastReceiver

    有了自己的BroadcastReceiver子类之后,需要对其进行注册。注册BroadcastReceiver的方法有两种,分别是:

    • 静态注册。
    • 动态注册。

    静态注册

    所谓静态注册,就是将触发器组件的相关信息写在应用的AndroidManifest.xml文件中。通过这种方式注册的广播称之为常驻型广播,也就是说如果应用程序关闭了,如果有相应的事件触发,该BroadcastReceiver的onReceive方法还是会被系统自动调用运行。

    例如,需要监听开机事件、来电事件、新消息事件时,均是采用静态注册。示例代码如下:

            <receiver
                android:name=".MyBroadcastReceiver"
                android:enabled="true"
                android:exported="true" >
                <intent-filter>
                    <action android:name="android.intent.action.MyBroadcastReceiver" />
    
                    <category android:name="android.intent.category.DEFAULT" />
                </intent-filter>
            </receiver>

    这里需要介绍一下android:enabled和android:exported这两个属性的作用。

    • android:enabled:是否这个broadcast receiver能由系统实例化。默认为true。
    • android:exported:是否broadcast receiver能够接收来自外部应用的消息。能接收则为true,否则为false。如果为false,则该broadcast receiver只能接收来自同一个应用或者相同用户ID的应用的组件发送的消息。该默认值依赖于broadcast receiver是否包含intent filters。如果缺少intent filter,意味着它只能通过精确指定类名来调用(ps:通常这种情况意味着该broadcast receiver只能在应用内部使用,因为外部应用是无法精确知道该类名的),因此,这种情况下默认值为false。如果有intent filter的配置,则说明broadcast receiver能接收来自系统其他应用包含该intent的广播,因此,这种情况默认值为true。

    动态注册

    动态注册是在.java文件中进行注册。通过这种方式注册的广播称之为非常驻型广播,它会跟随Activity的生命周期。通常,我们会在Activity的onCreate方法中对其进行初始化,在onResume方法中对其进行注册,在onPause方法中对其取消注册。示例代码如下:

    public class MainActivity extends Activity{
        private MyBroadCastReceiver myBroadCastReceiver;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            setContentView(R.layout.settings_main_prefs);
            // 实例化广播
            myBroadCastReceiver = new MyBroadCastReceiver();
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            Log.e("TAG", "main activity on resume!");
            // 注册广播,监听特定的intent
            registerMyBroadcastReceiver();
        }
    
        private void registerMyBroadcastReceiver() {
            IntentFilter filter = new IntentFilter();
            filter.addAction("android.intent.action.MyBroadcastReceiver");
            registerReceiver(myBroadCastReceiver, filter);
        }
    
        @Override
        protected void onPause() {
            super.onPause();
            Log.e("TAG", "main activity on Pause!");
            // 注销广播
            unregisterReceiver(myBroadCastReceiver);
        }
    }

    广播的发送

    BroadcastReceiver用来广播的接收,那这里还需要介绍一下广播的发送。

    Android的事件广播有两种模式:

    • 一种是通过Context.sendBroadcast方法进行发送,这被称为普通广播模式。在这种模式下,所有注册了该广播事件的BroadcastReceiver均会获得事件通知,并发地在各自的应用进程中执行。
    • 另一种是通过Context.sendOrderedBroadcast方法进行发送,被称为有序广播模式。所有监听该事件的触发器组件,都会依照设定的优先级进行排序,从高到低依次处理该事件。高优先级的触发器组件可以通过BroadcastReceiver.abortBroadcast方法来终止这个广播事件的传播,从而,使低优先级的触发器组件没有机会在处理该事件了。

    可以通过在AndroidManifest.xml文件中的标签中增加android:priority属性设置优先级,也可以在IntentFilter类实例中调用setPriority方法设置优先级。

    展开全文
  • 通用触发器系统

    千次阅读 2011-04-19 20:31:00
    集中化的触发器系统能够根据每一个触发器消息的优化级和影响范围对其进行过滤,从而保证对于每一个智能体而言,只处理它能力所及范围内的拥有最高优先级的触发器消息。触发器消息是游戏设计者所希望的使游戏中的智能...

    Jeff Orkin ———— Monolith Productions

    jorkin@blarg.net

    游戏中的触发器系统主要负责两个任务:对游戏中的所有的智能体 (agents) 响应事件 (event) 进行追踪;使智能体对这些事件做出响应时的处理开销最小化。集中化的触发器系统能够根据每一个触发器消息的优化级和影响范围对其进行过滤,从而保证对于每一个智能体而言,只处理它能力所及范围内的拥有最高优先级的触发器消息。

    触发器消息是游戏设计者所希望的使游戏中的智能体做出反应的任何 " 刺激源 " ([Nilsson98],[Russell95]) 。在动作类游戏中,触发器消息可以是会影响智能体行为的任何听觉和视觉刺激,例如枪声、爆炸、临近的敌人或者尸体。触发器消息也可以由游戏中涉及到智能体的非生命物体发出,例如游戏中的操纵杆和控制平台。触发器消息可以通过许多种途径来实现,包括游戏代码、脚本、控制台命令或动画关键帧。智能体可以确定那些种类的触发器消息对于它是有意义的。

    集中化的优点

    如果不使用集中化触发器系统的话,那就需要对事件进行轮询 ( polling ) 。轮询有两个显著的缺点。首先,轮询要求每一个智能体查询整个游戏世界来寻找感兴趣的事件。例如,如果一个智能体要对敌人的枪声做出反应,它就必须遍历游戏中的每一个角色,询问每一个角色最后一次开火的信息。这就要求每一个智能体都额外的保存任何感兴趣的历史数据。而这些复杂度为 O(n ² ) 的查询操作的结果很可能是最近根本没有任何人开枪。其次,采用轮询制的话,为了减轻 CPU 的负荷,某些智能体会处于休眠状态,那么这时即使一枚火箭呼啸着从它身边飞过,它也根本无法做出相应的反应。

    而在集中化系统中,在事件发生的时候,相应的触发器信息就被注册 ( register ) 了。在每一个时间周期,系统只需要遍历智能体列表。然后对每一个智能体,系统采用一系列的测试来决定是否对当前存在的任何触发器信息感兴趣。如果某一个智能体对当前所有的触发器信息都不感兴趣,那么他就根本不需要任何的额外处理。更为重要的时,系统可以根据触发器消息的类型和范围来过滤触发器消息。

    结合了分组的触发器消息过滤在许多情况下是非常有效的,这在本文的最后会详细说明。通过对当前的触发器信息的优先级进行排序,系统可以保证在任何一个时刻,智能体只对最重要的事件进行相应。例如,当一个敌人已经站在你面前的时候,你根本不会再去理会远处的脚步声了。集中化系统比较轮询系统具有更高的通用性,可重用性和可扩展性。这是因为当一个新的触发器信息被引入系统时,几乎不必编写任何特殊的编码来处理这一新的类型。

    定义一个触发器信息

    触发器信息使用了位标志 ( bit flag ) 来枚举其类型,并且定义了一组变量来描述其所需参数。下列的 TriggerRecordStruct 结构就定义了一个触发器消息的示例。

    Struct TriggerRecordStruct

    {

           EnumTriggerType                      eTriggerType;

           Unsigned long                            nTriggerID;

           Unsigned long                            idSource;

           Vector                                        VPos;

           Float                                           fRadius;

           Unsigned long                            nTimeStamp;

           Unsigned       long                      nExpirationTime;

           Bool                                            bDynamicSourcePos;

           …

    };

    触发器消息的类型是通过位标示来枚举的。在每一个智能体中都记录了其感兴趣的所有触发器消息,这是通过一个将这些感兴趣的触发器消息的位标志组合而来的 unsigned long

    类型的变量来实现的。下面是一个动作游戏的触发器消息类型示例。

           Enum EnumTriggerType

    {

           kTrig_None                        = 0 ,

           kTrig_Explosion                 = ( 1 << 0 ) ,

           kTrig_EnemyNear               = ( 1 << 1 ),

           kTrig_Gunfire                     = ( 1 << 2 ),

    };

    位标志的组合使得智能体可以确定要注意和忽视的触发器消息。智能体还可以在游戏中改变某一个位标志来暂时忽略和注意某一类触发器消息。例如一个只对声音做出相应反应的“盲智能体”可以通过所有声音触发器消息位标示的逻辑与来定义,这就同时忽略了所有视觉触发器消息。

    dwTriggerFlags   =   kTrig_Explosion   |   kTrig_Gunfire ;

    触发器消息 ID 是在这个触发器消息注册时由触发器系统分配的唯一标示符。触发器消息 ID 使得在以后的操作中可以引用这个触发器消息,这在后面的删除触发器消息中会谈到。

    ID(source ID ) 是创建了这个触发器消息的游戏对象的 ID 。这个源可以是开枪了的游戏角色,或者是爆炸了的一颗地雷。相应触发器消息的智能体必须知道究竟是谁产生了这个消息,从而可以开枪还击或者快速逃离爆炸地点。

    每一个触发器消息在游戏世界中都有其位置和范围半径,它只会影响到其范围半径之内的智能体。大多数的触发器消息是静态的,但是有些却是持续移动的。如果 bDynamicSourcePos 为真,则表明这个触发器消息是移动的,每一个时间周期都必须刷新它的位置。这个属性使得一个移动的目标只需要注册一个触发器消息,而不是每一个它移动时都注册一个新的触发器消息。敌人靠近就是这样的一个例子,它必须提醒智能体周围有敌人,因此这类触发器消息的位置必须跟踪其产生源的位置。

    触发器消息只生存一段特定的时间片断。每一个触发器消息都记录了它的创建时间和删除事件。在本书提供的代码中,时间是通过 Windows 多媒体计时器 timeGetTime() 函数返回的毫秒级时间。如果生存时间为 0 ,那么意味着这个触发器消息永远存在,或者是通过不同于定时的其他系统操作删除的。例如,如果智能体在到达某处时会拉动一个控制杆,那么控制杆就可以注册一个永不过期的触发器消息,知道它被拉动时,这个触发器消息才被删除。

    触发器消息系统

    触发器消息系统是一个存储现存的触发器消息记录的类。它提供了注册、删除和更新触发器消息的方法。

    class CTriggerSystem

    {

    public:

           CTriggerSystem();

           ~CTriggerSystem();

           unsigned long RegisterTrigger( EnumTriggerType _eTriggerType,

                                       unsigned long _nPriority, unsigned long _idSource,

                                       const Vector& _vPos, float _fRadius, float _fDuration,

                                       bool _bDynamicSourcePos );

           void RemoveTrigger( unsigned long nTriggerID );

           void Update();

    Private:

           TRIGGER_MAP m_mapTriggerMap;

           BOOOL        m_bTriggerCriticalSection;

    };

    当前的触发器信息被存储在一个 STL ( stand template library ) 多重映射表 ( multimap ) 中,根据优先级排序。

    typedef std::multimap<unsigned short, TriggerRecordStruct*, std::greater<unsigned short>>TRIGGER_MAP;

    在这里使用 greater<unsigned short> 比较函数而不是默认的 less< unsigned short > 比较函数是为了保证具有更高优先级的触发器消息排列在前面。多重映射表允许重复的关键字,这意味着不同的触发器消息可以拥有相同的优先级。

    注册触发器消息

    触发器消息是通过调用 RegisterTrigger() 来注册到系统中的。 RegisterTrigger() 创建一个新的触发器消息并且设置其属性。我们并不一定需要在参数中传递所有的属性值。常用的做法是在资源文件中预先定义好触发器消息的类型,从而只需要传递一个结构的引用即可。

    unsigned long CTriggerSystem::RegisterTrigger(EnumTriggerType _eTriggerType, unsigned long _nPriority, unsigned long _idSource,   const Vector& _vPos,   float _fRadius,

    float _fDuration, bool _bDynamicSourcePos )

    {

           // 创建触发器消息记录,并且赋值

           TriggerRecordStruct* pTriggerRecord = new TriggerRecordStruct( _eTriggerType, _idSource, _vPos, _fRadius, _fDuration, _bDynameicSourcePos);                                   

    // 根据优先级存储触发器消息记录

    m_mapTriggerMap.insert( TRIGGER_MAP::value_type( _nPriority, pTriggerRecord));

    // 返回新建的触发器消息的唯一标识符

    Return   pTriggerRecord -> nTriggerID;

    }

    这个函数返回给调用者唯一的触发器消息 ID 。可以保证这个 ID ,然后用来引用这个触发器消息实例,特别是在删除这个触发器消息的时候。

    删除触发器消息

    通常,在游戏世界中删除某个现存的触发器消息是必须的。例如:如果某个触发器消息的产生源 " " 了,那么这个触发器消息也就没有意义了。有些触发器消息可以被激活或者休眠。一个已经被拉动的操纵杆在游戏中对于智能体而言不再有任何用处。某个角色通过掩饰自己而暂时停止发出敌人接近类型的触发器消息。在所有这些情况中,可以通过调用 RemoveTrigger() 来删除当前的某个触发器消息。

    void CTriggerSystem::RemoveTrigger( unsigned long nTriggerID )

    {

    TRIGGER_MAP::iterator it = _mapTriggerMap.begin();

    while( it != m_mapTriggermap.end() )

    {

           if( it->second->nTriggerID == nTriggerID )

           {

                  delete (it->second);

                  return;

           }

           else

                  ++it;

    }

    }

    刷新触发器消息系统

    触发器消息系统的核心是 Update() 函数。这个函数删除过期的触发器消息,刷新动态位置的触发器消息,并且通知与某个触发器消息有关的所有智能体。

    void CTriggerSystem::UpDate()

    {

           CAgent *pAgent = NULL;

           float fDistance - 0.f;

           TriggerRecordStruct* pRec;

           TRIGGER_MAP::iterator it;

           unsigned long nCurTime = timeGetTime();

           // 删除过期的触发器消息,更新未过期的运动触发器消息的位置信息。

           it = m_mapTriggerMap.begin();

           while ( it != m_mapTriggerMap.end() )

           {

                  pRec = it->second;

                  if( (pRec->nExpirationTime != 0) && (pRec->nExpirationTime < nCurTime) )

                  {

                         delete (pRec);

                         it = m_mapTriggerMap.erase(it);

                  }

                  else

                  {

                         // 刷新动态标示为真的触发器消息,重置时间戳

                         if(rRec-> bDynamicSourcePos == true )

                         {

                                UpdatePos( pRec->vPos );

                                pRec->nTimeStamp = nCurTime;

                         }

                         ++it;

                  }

           }

           // 触发器智能体

           for( unsigned long i = 1; i<g_nNumAgents; ++i )

           {

                  pAgent = g_pAgentList[i];

                  // 检查是否需要更新

                  If ( nCurTime > pAgent->GetNextTriggerUpdate() )

                  {

                         pPagent->SetNextTriggerUpDate(nCurTime);

                         // 遍历所有触发器消息

                         for( it = m_mapTriggerMap.begin(); it != m_mapTriggerMap.end(); ++it )

                         {

                                pRec = it->second;

                                // 是否响应

                                if( !(pRec->eTriggerType & pAgent->GetTriggerFlags()) )

                                       continue;

                                // 产生源不是智能体自己吧

                                if(pRec->idSource == i)

                                       continue;

                                // 如果这个智能体响应这个触发器消息,那么 HandleTrig() 返回真

                                if( pAgent->HandleTrig(pRec))

                                {

                                       // 在任何时候,只响应最高优先级的触发器消息

                                       break;

                                }

                         }

                  }

           }

    }

    首先, Update() 遍历所有当前的触发器消息。删除过期的触发器消息;对于拥有动态源的触发器消息,更新其位置和时间戳,而不用创建新的触发器消息。

    然后, Update() 遍历所有的智能体通知它们相关的触发器消息。这个循环包含了一系列的 if 语句,在进一步的复杂处理前逐步进行必要的过滤。最先的去除是根据智能体的更新时间来进行。每一个智能体都有一个更新速率,用来防止它太过频繁,甚至于每一帧都进行更新。通常的更新速率为每秒 15 次。不同智能体的更新时间要错开,这样才能保证更新速率相同的智能体不会总是在同一个时刻更新。

    对于在当前周期中需要更新的智能体而言, Update() 遍历当前的触发器消息。检查每一个触发器消息是否属于这个智能体感兴趣的触发器消息类型。智能体只对感兴趣的触发器消息进行进一步处理。

    如果智能体对某个触发器消息有兴趣,而且这个触发器消息不是它自身创建的,这才执行最复杂的距离检查。

    如果某一个触发器消息通过了智能体的所有检查,那么这个智能体的 HandleTrig() 函数被调用来处理这个触发器消息。 HandleTrig() 可能会进一步的计算和判断来确定是否对这个触发器消息做出反应。返回值的 true false 用来告诉 Update() ,它是否进行了响应。一个智能体在某一个时刻只能响应一个触发器消息,因此如果已经响应了一个触发器消息,则停止检查其他的触发器消息。如果返回值是 false ,则循环继续,允许智能体响应其他触发器消息。因为触发器消息是根据优先级排序的,因此在任一时刻,保证了只响应具有最高优先级的触发器消息。“只响应一个触发器消息”的背后存在着一个假设:触发器消息会使智能体进入有限状态的某个状态。而这个状态控制着智能体如果对这个触发器消息代表的事件进行响应。只响应最高优先级的触发器消息,保证了智能体采取当前状况下的最正确的行为方式。如果智能体采用的时不同于有限状态机的其他结构,而且必须支持同时对多个触发器消息的响应,那么可以通过返回 false 来继续查找较低优先级的其他触发器消息。在任何情况下,触发器消息的生存周期都必须大于一个时间周期。只有这样,智能体才能在处理完最高优先级的触发器消息之后再来处理较低优先级的触发器消息。

    智能体的层次分组

    可以通过智能体的分组来最大化的体现触发器系统过滤的好处。如果游戏中有众多的智能体,对所有智能体进行触发器消息检查是不可能的。取而代之的是对智能体组进行触发器消息检查。触发器消息系统只需要一些细微的改动,就可以对智能体组进行处理。

    智能体可以根据各种各样的标准来分类,包括位置、更新速率、种族或者派别。触发器信息系统可以循环地测试这些分组。如果系统确定某个组对当前的一个触发器消息感兴趣,

    那么可以进一步测试组内的每一个成员。组内的成员也可以是智能体组,浙江欧形成了一个层次结构。例如,智能体可以先根据种族来划分大类,然后又根据位置来划分小类。分组使得只通过一次检测就可以过滤掉大量无关的智能体。例如可以有效地忽略掉遥远的触发器消息,或者中立的智能体可以忽略掉绝大多数的触发器消息,从而最小化其处理开销。

           在实现上,支持智能体组的类可以通过支持单个智能体的类派生而来。当智能体形成一个组时,通过设定组的成员变量来反应组内成员的属性组合。例如:组对应的感兴趣的触发器消息的标志位可以通过组内所有成员的标志位的组合得到。如果一个组内有两个成员,一个对爆炸有反应,另一个对敌人的位置敏感,那么组的标志位就类似如下 :

    pPgroup->SetTriggerFlags( pAgent0 -> GetTriggerFlags() , pAgent0 -> GetTriggerFlags() )

    // 上述语句等价于

    DwTriggerFlags  =  kTrig_Explosion  |  kTrig_EnemyNear ;

           组的位置可以采用类似的模拟来处理,其位置被设置成所有成员位置的平均值。智能体需要增加一个变量来表示其半径,这个半径保卫了组内所有成员的位置。触发器消息系统的距离检测可以改为检查组的范围半径和触发器消息的范围半径是否相交来确定。

           触发器消息系统的 Update() 函数的参数改为使用一个指向智能体列表的指针,而不是全局的智能体列表。 Update() 首先检查智能体组的列表。组的 HandleTrig() 函数然后再次调用 Update(), 传入组内成员列表。重复这个过程就可以完成从智能体组到智能体个体的递归过程。

    触发所有可能性

    一旦上述的基本的触发器消息系统得以应用,游戏中就可以实现无穷可能性的人工智能了。触发器消息可以用来提醒智能体枪声和爆炸;一个受了伤的智能体可以利用活动位置的触发器消息来寻求同样的帮助;甚至可以利用附着在无生命对象 ( 例如 ) 上的触发器消息来检测游戏者的交互行为。

    让我们想象如下的场景:游戏者开枪打开了一扇锁着的门。枪为枪声创建了一个触发器消息,触发了附近的一个电脑角色智能体。这个智能体走到发出声音的位置,注意到了游戏者。智能体通过一个由游戏者角色的外表创建的活动位置的触发器消息看见了游戏者,它沿着这个活动触发器消息追击游戏者。游戏者打死了这个电脑角色智能体,尸体创建了一个永久的触发器消息。另一个电脑角色智能体经过尸体,响应了这个触发器消息。它起了疑心,因此激活了脚印触发器消息的标志位。而由于游戏者受了伤,在他的脚印位置上留下了一连串的触发器消息。当电脑角色智能体沿着脚印触发器消息追踪的时候,他经过了一个报警按钮。这个按钮在其创建时就注册了一个永久的触发器消息。智能体响应了按钮的触发器消息,于是走到按钮边,按响了警报。从而触发其他的电脑角色智能体去搜寻游戏者。

    这只是触发器消息系统如何用来制作有趣的游戏的一个很简单的例子。触发器消息系统的功能是极为强大的,就看游戏设计者的想象力和创造力有多么丰富了 !

    参考文献

    [ Musser 01 ] Musser David R Derge Gillmer J, Sainni Atul. STL Tutorial and Reference Guide : C++ Programming with the Standard Templage Library 2nd Ed. Addison – Wesley Publishing Co . , 2001

    [ Nillsson 98 ] Nillson Nils J. Artificial Intelligence:   A New Synthesis. Morgan Kaufman Publishers, Inc . , 1998

    [ Russell 95 ] Russell Stuart Norvig Peter . Artificial Intelligence. A Modern  Approach. Prentice Hall, 1995

    展开全文
  • 很早就想自己写写Oracle的函数和触发器,最近一个来自课本的小案例给了我这个机会。现在把我做的东西记录下来,作为一个备忘或者入门的朋友们的参考。    案例介绍:  招投标管理系统(数据库设计)。  数据...

      很早就想自己写写Oracle的函数和触发器,最近一个来自课本的小案例给了我这个机会。现在把我做的东西记录下来,作为一个备忘或者入门的朋友们的参考。

      

      案例介绍:

        招投标管理系统(数据库设计)。

        数据表有以下两张:

          招标书(招标书编号、项目名称、招标书内容、截止日期、状态)。

          投标书(投标书编号、招标书编号、投标企业、投标书内容、投标日期、报价、状态)。

          “招标书编号”为字符型,编号规则为 ZBYYYYMMDDNNN, ZB是招标的汉语拼音首字母,YYYYMMDD是当前日期,NNN是三位流水号。

          “投标书编号”为字符型,编号规则为TB[11位招标书编号]NNN。

     

      经过分析,我们可以得知两张表的关系。我们先创建数据结构,比如:

     1 CREATE TABLE TENDER
     2 (
     3   TENDER_ID    VARCHAR2(50) PRIMARY KEY,
     4   PROJECT_NAME VARCHAR2(50) NOT NULL UNIQUE,
     5   CONTENT      BLOB,
     6   END_DATE     DATE NOT NULL,
     7   STATUS       INTEGER NOT NULL
     8 );
     9 CREATE TABLE BID
    10 (
    11   BID_ID    VARCHAR2(50) PRIMARY KEY,
    12   TENDER_ID VARCHAR2(50) NOT NULL,
    13   COMPANY   VARCHAR2(50) NOT NULL,
    14   CONTENT   BLOB,
    15   BID_DATE  DATE NOT NULL,
    16   PRICE     INTEGER NOT NULL,
    17   STATUS    INTEGER NOT NULL
    18 );
    19 ALTER TABLE BID ADD CONSTRAINT FK_BID_TENDER_ID FOREIGN KEY(TENDER_ID) REFERENCES TENDER(TENDER_ID);

      然后是生成招标的函数:

     1 CREATE OR REPLACE 
     2 FUNCTION "createZBNo" RETURN VARCHAR2
     3 AS
     4 hasCount NUMBER(11,0);
     5 lastID VARCHAR2(50);
     6 lastTime VARCHAR2(12);
     7 lastNo NUMBER(3,0);
     8 curNo NUMBER(3,0);
     9 BEGIN
    10     -- 查询表中是否有记录
    11     SELECT "COUNT"(TENDER_ID) INTO hasCount FROM TENDER;
    12     IF hasCount > 0 THEN
    13         -- 查询必要信息
    14         SELECT TENDER_ID INTO lastID FROM TENDER WHERE ROWNUM = 1 ORDER BY to_number(to_char(scn_to_timestamp(ORA_ROWSCN),'yyyyMMddhh24mmss'),'99999999999999') DESC;
    15         SELECT "SUBSTR"(lastID, 3, 8) INTO lastTime FROM dual;
    16         -- 分析上一次发布招标信息是否是今日
    17         IF ("TO_CHAR"(SYSDATE,'YYYYMMDD') = lastTime) THEN
    18             SELECT "TO_NUMBER"("SUBSTR"(lastID, 11, 13), '999') INTO lastNo FROM dual;
    19             -- 如果是今日且流水号允许新增招标信息
    20             IF lastNo < 999 THEN
    21                 SELECT lastNo + 1 INTO curNo FROM dual;
    22                 RETURN 'ZB'||lastTime||"LPAD"("TO_CHAR"(curNo), 3, '0');
    23             END IF;
    24                 -- 流水号超出
    25                 RETURN 'NoOutOfBounds!Check it!';
    26         END IF;
    27             -- 不是今日发布的招标信息,今日是第一次
    28             RETURN 'ZB'||"TO_CHAR"(SYSDATE,'YYYYMMDD')||'001';
    29     END IF;
    30             -- 整个表中的第一条数据
    31         RETURN 'ZB'||"TO_CHAR"(SYSDATE,'YYYYMMDD')||'001';
    32 END;

      然后是投标书的编号生成函数:

     1 CREATE OR REPLACE 
     2 FUNCTION "createTBNo" (ZBNo IN VARCHAR2)
     3 RETURN VARCHAR2
     4 AS
     5 hasCount NUMBER(11,0);
     6 lastID VARCHAR2(50);
     7 lastNo NUMBER(3,0);
     8 curNo NUMBER(3,0);
     9 BEGIN
    10     -- 查看是否已经有了对于该想招标的投标书
    11     SELECT "COUNT"(BID_ID) INTO hasCount FROM BID WHERE BID_ID LIKE 'TB'||ZBNo||'___' AND ROWNUM = 1 ORDER BY to_number(to_char(scn_to_timestamp(ORA_ROWSCN),'yyyyMMddhh24mmss'),'99999999999999') DESC;
    12     IF hasCount > 0 THEN
    13         -- 有了
    14         SELECT BID_ID INTO lastID FROM BID WHERE BID_ID LIKE 'TB'||ZBNo||'___' AND ROWNUM = 1 ORDER BY to_number(to_char(scn_to_timestamp(ORA_ROWSCN),'yyyyMMddhh24mmss'),'99999999999999') DESC;
    15             SELECT "TO_NUMBER"("SUBSTR"(lastID, 16,18),'999') INTO lastNo FROM dual;
    16             -- 流水号没超出
    17             IF lastNo < 999 THEN
    18                 SELECT lastNo + 1 INTO curNo FROM dual;
    19                 RETURN 'TB'||ZBNo||"LPAD"("TO_CHAR"(curNo),3,'0');
    20             END IF;
    21                 RETURN 'NoOutOfBounds!Check it!';
    22     END IF;
    23         -- 没有投标书对该招标书
    24         RETURN 'TB'||ZBNo||'001';
    25 END;

      然后在两个表中注册触发器,当新增数据的时候动态生成编号!

      招标书触发器,用于动态生成招标书编号:

     1 CREATE OR REPLACE 
     2   TRIGGER newTender
     3   BEFORE INSERT 
     4   ON TENDER
     5   FOR EACH ROW
     6 BEGIN
     7     -- 如果生成编号失败
     8   IF (LENGTH("createZBNo") <> 13) THEN
     9         -- 此处根据我的提示信息报错可以直接如下操作
    10         -- :NEW.TENDER_ID := NULL;
    11     RAISE_APPLICATION_ERROR(-20222,"createZBNo");
    12   END IF;
    13         -- 如果生成编号成功,将编号注入查询语句中
    14      :NEW.tender_id :="createZBNo";
    15 END;

      然后是投标书的触发器:

     1 CREATE OR REPLACE 
     2   TRIGGER newBid
     3   BEFORE INSERT 
     4   ON BID
     5   FOR EACH ROW
     6 BEGIN
     7   IF (LENGTH("createTBNo"(:NEW.TENDER_ID)) <> 18) THEN
     8     RAISE_APPLICATION_ERROR(-20222,"createTBNo"(:NEW.TENDER_ID));
     9   END IF;
    10      :NEW.BID_ID :="createTBNo"(:NEW.TENDER_ID);
    11 END;

      然后插入数据测试吧:

      

     

      

     

      以上只是个人的一些观点,如果您不认同或者能给予指正和帮助,请不吝赐教。

     

        

    展开全文
  • ebs 二次开发 触发器

    2021-03-03 10:46:08
    经常需要修改的触发器: ACCEPT APP_STANDARD.EVENT(‘ACCEPT’); 这个触发器处理菜单或工具条上调用Save and Proceed (保存并继续)动作。它执行保存,并移动到指定当作第一个导航块的块上。 替换这个触发器中的...
  • 1创建触发器 create trigger 触发器名称 on 表名 for delete,update,insert --触发条件可多选或单选 as T-SQL语句 2删除触发器 drop trigger 触发器名称[,...] 3重命名触发器 用查询分析器重命名 exec sp_...
  • 魔兽争霸触发器Trigger解析

    千次阅读 2013-04-15 11:30:33
    大部分事件可以直接指定,但有部分事件却需要后期添加或者必须要有指定类型的变量才可以添加,后期添加也比较方便,后面会介绍到“动态注册事件”,这里我们先说一下具体的事件内容。  还有需要注意到触发的关
  • 使用触发器记录oracle用户登陆信息

    千次阅读 2013-10-28 11:17:30
    Oracle 提供了强大的审计功能,可以针对用户级,系统...基于这种情形,使用基于数据库级别的触发器可以简单的实现这个需求。 1、实现代码--创建表用于存储登陆或登出的统计信息 CREATE TABLE stats$user_log ( user_
  • mysql 触发器 http 请求

    千次阅读 2018-01-19 19:31:26
    1.触发程序不能调用将数据返回客户端的存储程序,也不能使用采用CALL语句的动态SQL语句,但是允许存储程序通过参数将数据返回触发程序,也就是存储过程或者函数通过OUT或者INOUT类型的参数将数据返回触发器是可以的...
  • 第9章存储过程和触发器 • 9.1 存储过程 9.1.1 存储过程的概念 u 存储过程是指封装了可重用代码的、存储在服务器上的程序模块或例程。存储过程是数据库对象之一,它类似于其他高级编程语言中的过程或子程序,...
  • EBS开发——经常需要修改的触发器

    千次阅读 2014-12-25 16:26:03
    经常需要修改的触发器: ACCEPT APP_STANDARD.EVENT(‘ACCEPT’); 这个触发器处理菜单或工具条上调用Save and Proceed (保存并继续)动作。它执行保存,并移动到指定当作第一个导航块的块上。 替换这个...
  • 标准做法是删除触发器,当再次需要它时再重新创建,但如果您必须跟踪许多触发器,那就有点为难 了。(现在,我该把那些触发器的源代码保存到哪里呢?) 本文提供了三种解决这个问题的方法: 方法 1:对特定用户禁用...
  • 就像作业一样,触发器相对来说也很容易处理,但是确实包含了各种可定制的选项,在充分利用Quartz.NET之前,您需要了解并理解这些选项。此外,如前所述,有不同类型的触发器,您可以选择以满足不同的调度需求。 常见...
  • 虚拟的表,只包含使用时动态检索数据的查询,而自身不包含任何数据。 作用: 1》sql简化。 2》安全性。 3》重用sql语句。 4》使用表的部分而不是整个表。 5》保护数据,可以给用户授予表的特定...
  • Quartz.NET--Trigger 触发器

    千次阅读 2013-03-04 10:50:49
    Quartz.NET 实现了3个具体的触发器类,SimpleTrigger 简单地在某一时间重复执行多少次,NthIncludedDayTrigger 在每一年、月、周的第几天(Nth)执行作业,CronTrigger 使用 Unix 平台下的'cron-like’表达式来实现...
  • 触发器和游标的用法

    2010-01-21 15:46:00
    declare @d datetimeset @d=getdate()select [语句执行花费时间(毫秒)]=datediff(ms,@d,getdate())查找数据库中的存储过程select name from sysobjects where xtype=TR如何禁用、启用触发器禁用:alter table 表名 ...
  • Oracle Forms开发之触发器(trigger)

    千次阅读 2014-08-27 14:13:48
    经常需要修改的触发器: ACCEPT APP_STANDARD.EVENT(‘ACCEPT’); 这个触发器处理菜单或工具条上调用Save and Proceed (保存并继续)动作。它执行保存,并移动到指定当作第一个导航块的块上。 替换这个...
  • 引言 触发框图形是让实体等游戏对象实现沟通的 Vision 内置系统的一部分。 触发框的设计是为了在某个预定义对象进入其边界时发送一个事件,例如主镜头进入触发框...使用 API 的对象组件系统,可用代码定义触发器
  • MySQL中的特性-视图,触发器,存储过程 MySQL存储过程 应用场景 我们之前前使⽤的⼤多数SQL语句都是针对⼀个或多个表的单条语句。并⾮所有操作都这么简单,经常会有⼀个完整的操作需要多条语句才能完成。 例如以下的...
  • ------------------------2013-5-20------------------------数据库触发器内置程序包 是pl/sql块或存储过程,dml操作时触发,隐式执行,可能具有声明部分和异常处理部分。 组成:语句,主体,限制。类型:应用程序...
  • 1、org.quartz.Trigger 是基接口,具有所有触发器通用的属性,使用 org.quartz.TriggerBuilder 类实例化实际触发器。 2、触发器有一个关联的 TriggerKey,它应该在单个 Scheduler 中唯一标识它们。 3、多个触发器...
  • MySQL中的特性-视图,触发器,存储过程 MySQL存储过程 应用场景 我们之前前使⽤的⼤多数SQL语句都是针对⼀个或多个表的单条语句。并⾮所有操作都这么简单,经常会有⼀个完整的操作需要多条语句才能完成。 例如以下的...
  • Quartz.Net进阶之二:关于触发器的更多信息 原文:Quartz.Net进阶之二:关于触发器的更多信息 与作业一样,触发器相对容易使用,但是在您可以充分利用Quartz.NET之前,确实需要了解和理解各种可自定义...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 11,231
精华内容 4,492
关键字:

如何动态注册触发器