JMS监听Oracle AQ
- 该文档中,oracle版本为11g,jdk版本1.8,java项目为maven构建的springboot项目,springboot的版本为2.1.6,并使用了定时任务来做AQ监听的重连功能,解决由于外部原因导致连接断裂之后,需要手动重启项目才能恢复连接的问题
一、创建队列
1.1.管理员登录执行
- 管理员登录,执行授权操作,oracle使用队列需要单独的授权,默认未开启,须手动开启,授权命令如下,username使用自己的用户名即可
GRANT EXECUTE ON SYS.DBMS_AQ to 'username';
GRANT EXECUTE ON SYS.DBMS_AQADM to 'username';
GRANT EXECUTE ON SYS.DBMS_AQ_BQVIEW to 'username';
GRANT EXECUTE ON SYS.DBMS_AQIN to 'username';
GRANT EXECUTE ON SYS.DBMS_JOB to 'username';
1.2.用户登录执行执行
1.2.1. 创建消息负荷payload
- 创建的此type用来封装队列所带的,根据实际需求进行创建
CREATE OR REPLACE TYPE TYPE_QUEUE_INFO AS OBJECT
(
param_1 VARCHAR2(100),
param_2 VARCHAR2(100)
)
1.2.2. 创建队列表
- 创建对列表,并指定队列数据的类型,队列表名自定义即可,数据类型使用上面刚创建的type
begin
sys.dbms_aqadm.create_queue_table(
queue_table => 'QUEUE_TABLE',
queue_payload_type => 'TYPE_QUEUE_INFO',
sort_list => 'ENQ_TIME',
compatible => '10.0.0',
primary_instance => 0,
secondary_instance => 0);
end;
1.2.3. 创建队列并启动
- 创建名称为QUEUE_TEST的队列,并指定对列表名【同一个oracle用户下,可以有多个对列表,同一个对列表中,可以有多个队列】
begin
sys.dbms_aqadm.create_queue(
queue_name => 'QUEUE_TEST',
queue_table => 'QUEUE_TABLE',
queue_type => sys.dbms_aqadm.normal_queue,
max_retries => 5,
retry_delay => 0,
retention_time => 0);
end;
- 刚创建的队列的状态默认是未开启的,需要手动开启一下,同理,存在删除、停止等操作
begin
-- 启动队列
sys.dbms_aqadm.start_queue(
queue_name => 'QUEUE_TEST'
);
-- 暂停队列
--sys.dbms_aqadm.STOP_QUEUE(
-- queue_name => 'QUEUE_TEST'
--);
-- 删除队列
--sys.dbms_aqadm.DROP_QUEUE(
-- queue_name => 'QUEUE_TEST'
--);
-- 删除对列表
--sys.dbms_aqadm.DROP_QUEUE_TABLE(
-- queue_table => 'QUEUE_TABLE'
--);
end;
1.2.4. 创建存储过程
- 储存过程的作用为把数据加载到队列中,生成的新的队列会自动添加进绑定的对列表中,等待消费者进行消费
CREATE OR REPLACE PROCEDURE pro_queue(param_1 VARCHAR2, param_2 VARCHAR2) as
r_enqueue_options DBMS_AQ.ENQUEUE_OPTIONS_T;
r_message_properties DBMS_AQ.MESSAGE_PROPERTIES_T;
v_message_handle RAW(16);
o_payload TYPE_QUEUE_INFO;
begin
-- 封装最终消息
o_payload := TYPE_QUEUE_INFO(param_1, param_2);
-- 入队操作,指定队列
dbms_aq.enqueue(queue_name => 'QUEUE_TEST',
enqueue_options => r_enqueue_options,
message_properties => r_message_properties,
payload => o_payload,
msgid => v_message_handle);
-- 出队操作
--dbms_aq.enqueue(queue_name => 'QUEUE_TEST',
-- dequeue_options => r_dequeue_options,
-- message_properties => r_message_properties,
-- payload => o_payload,
-- msgid => v_message_handle);
end pro_queue;
二、Java中JMS的使用
2.1. 项目配置
2.1.1. maven
<dependency>
<groupId>com.oracle</groupId>
<artifactId>jmscommon</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>orai18n</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>jta</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>aqapi_g</artifactId>
<version>1.2</version>
</dependency>
2.1.2. yml
datasource:
url: jdbc:oracle:thin:@ip:port/sid
username: **
password: **
queue:
aq:
# 该队列是否可用,用来控制队列的加载和重连,不可省略
enable: true
# 队列名称,不可省略
name: QUEUE_TEST
# 队列重连的定时任务对应的时间表达式,不可省略
cron: 0 */1 * * * ?
2.2. AQ初始化
- 在项目启动结束后立即运行此类,会根据所配置的队列名称监听对应的队列
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
/**
* @Title: MessageAQInit.java
* @Description: AQ 初始化
* @author wangqq
* @date 2020年6月28日 下午3:45:23
* @version 1.0
*/
@Component
public class MessageAQInit implements CommandLineRunner {
@Autowired
private MessageAQConfig aqConfig;
@Autowired
private MessageAQListener listener;
@Override
public void run(String... args) throws RuntimeException {
// 检查消息队列是否启用
if (aqConfig.enable) {
// 设置AQ的消息监听器
MessageAQConnection.setListener(listener);
// 设置oracle配置
if (!MessageAQConnection.initFactory(aqConfig)) {
throw new RuntimeException("Message Oracle AQ initialization failed!");
}
// 建立连接
if (!MessageAQConnection.establishConnection(aqConfig)) {
throw new RuntimeException("Message Oracle AQ connection failed!");
}
}
}
}
2.3. 配置信息类
- 配置类,将yml的配置文件转为java对象【时间表达式在代码中不会以对象属性的方式被使用,因此在该类中没有设置】
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @Title: MessageAQConfig.java
* @Description: ORACLE 消息队列配置
* @author wangqq
* @date 2020年6月28日 下午3:36:08
* @version 1.0
*/
@Component
public class MessageAQConfig {
/** 是否开启MessageAq功能 */
@Value("${queue.aq.enable}")
public Boolean enable;
/** 数据库用户名 */
@Value("${datasource.username}")
public String userName;
/** 数据库密码 */
@Value("${datasource.password}")
public String password;
/** 数据库地址url */
@Value("${datasource.url}")
public String url;
/** 队列名称 */
@Value("${queue.aq.name}")
public String queue;
}
2.4. AQ 连接工厂类
- AQ 链接的核心类,根据配置对象以及注入的监听对象,动态监听AQ队列
import javax.jms.Queue;
import javax.jms.Session;
import lombok.extern.slf4j.Slf4j;
import oracle.jms.AQjmsConnection;
import oracle.jms.AQjmsConnectionFactory;
import oracle.jms.AQjmsConsumer;
import oracle.jms.AQjmsSession;
/**
* @Title: MessageAQConnection.java
* @Description: AQ 连接
* @author wangqq
* @date 2020年6月28日 下午3:50:32
* @version 1.0
*/
@Slf4j
public class MessageAQConnection {
private static AQjmsConnectionFactory aQjmsConnectionFactory;
private static AQjmsConsumer aQjmsConsumer;
private static AQjmsSession aQjmsSession;
private static AQjmsConnection aQjmsConnection;
private static MessageAQListener listener;
/**
* 设置JMS监听器
*
* @param messageAqJmsListener
* @author wangqq
* @date 2020年7月6日 上午8:33:57
*/
public static void setListener(MessageAQListener messageAqJmsListener) {
listener = messageAqJmsListener;
}
/**
* 初始化 AQ 连接 Factory
*
* @param aqConfig 消息队列配置
* @return 是否成功
*/
public static boolean initFactory(MessageAQConfig aqConfig) {
try {
aQjmsConnectionFactory = new AQjmsConnectionFactory();
aQjmsConnectionFactory.setJdbcURL(aqConfig.url);
aQjmsConnectionFactory.setUsername(aqConfig.userName);
aQjmsConnectionFactory.setPassword(aqConfig.password);
return true;
} catch (Exception e) {
log.error(e.getMessage(), e);
return false;
}
}
/**
* 连接消息队列
*
* @param aqConfig 消息队列配置
* @return 是否成功
*/
public static boolean establishConnection(MessageAQConfig aqConfig) {
try {
aQjmsConnection = (AQjmsConnection) aQjmsConnectionFactory.createConnection();
aQjmsSession = (AQjmsSession) aQjmsConnection.createSession(false, Session.AUTO_ACKNOWLEDGE);
aQjmsConnection.start();
Queue queue = aQjmsSession.getQueue(aqConfig.userName, aqConfig.queue);
aQjmsConsumer = (AQjmsConsumer) aQjmsSession.createConsumer(queue, null, MessageORAData.getFactory(), null,
false);
aQjmsConsumer.setMessageListener(listener);
return true;
} catch (Exception e) {
log.error(e.getMessage(), e);
return false;
}
}
/**
* 关闭消息队列连接
*
* @return 是否成功
*/
public static boolean closeConnection() {
try {
aQjmsConsumer.close();
aQjmsSession.close();
aQjmsConnection.close();
return true;
} catch (Exception e) {
log.error(e.getMessage(), e);
return false;
}
}
}
2.5. 创建AQ 数据承载类
- 用来接收oracle队列中所带的参数,基本保证与数据库中的type格式相同即可
import lombok.Data;
/**
* @Title: Test.java
* @Description: AQ 数据承载类
* @author wangqq
* @date 2021-01-20 16:19:16
* @version 1.0
*/
@Data
public class Test {
private String param_1;
private String param_2;
}
2.6. 数据类型转换
- 将oracleAq所承载的数据,转化为我们自己需要的实例对象,及上述中的Test对象
package com.synjones.message.oracleaq;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Struct;
import com.synjones.message.vo.MessageInfoVo;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import oracle.sql.Datum;
import oracle.sql.ORAData;
import oracle.sql.ORADataFactory;
/**
* @Title: MessageORAData.java
* @Description: 数据类型转换类
* @author synjones
* @date 2018年12月3日 上午11:29:50
* @version 1.0
*/
@Slf4j
@NoArgsConstructor
public class MessageORAData implements ORAData, ORADataFactory {
@SuppressWarnings("unused")
private Object[] rawData = new Object[8];
private static final MessageORAData MESSAGE_FACTORY = new MessageORAData();
public static ORADataFactory getFactory() {
return MESSAGE_FACTORY;
}
@Override
public ORAData create(Datum datum, int sqlType) throws SQLException {
if (datum == null) {
return null;
} else {
try {
MessageORAData payOraData = new MessageORAData();
Struct aStruct = (Struct) datum;
payOraData.rawData = aStruct.getAttributes();
return payOraData;
} catch (Exception e) {
log.error(e.getMessage(), e);
return null;
}
}
}
@Override
public Datum toDatum(Connection arg0) throws SQLException {
return null;
}
/**
* 消息内容解析并封装
*
* @return
* @author wangqq
* @date 2020年7月6日 上午8:38:01
*/
public Test getContent() {
try {
return Test.builder()
.param_1(rawData[0] == null ? null : rawData[0].toString())
.param_2(rawData[0] == null ? null : rawData[0].toString())
.build();
} catch (Exception e) {
log.error(e.getMessage(), e);
return null;
}
}
}
2.7. AQ 监听
import javax.jms.Message;
import javax.jms.MessageListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
import oracle.jms.AQjmsAdtMessage;
/**
* @Title: JMSListener.java
* @Description: JMS监听ORACLEAQ的队列消息
* @author wangqq
* @date 2020年6月28日 上午11:23:42
* @version 1.0
*/
@Slf4j
@Component
public class MessageAQListener implements MessageListener {
@Override
public void onMessage(Message message1) {
AQjmsAdtMessage adtMessage = (AQjmsAdtMessage)message1;
try {
MessageORAData payload = (MessageORAData)adtMessage.getAdtPayload();
// 获取消息内容
Test test = payload.getContent();
// 个人业务代码
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
2.8. AQ 监控任务, 在AQ断开后重连
- 通过定时任务,定时查询是否有入队时间在5分钟之内的队列未被消费【队列入队后,会在对列表中产生一条数据,消费之后该数据会被清除掉】,若存在,则说明监听异常,需要重新创建连接监听队列
- 数据库对列表中的入队时间在本次测试中为0时区的时间,故而在代码中转换了一下时区,否则无法根据入队时间查询数据
import com.synjones.core.tool.utils.DateUtil;
import com.synjones.core.tool.utils.StringUtil;
import com.synjones.message.mapper.MessageAqMapper;
import lombok.extern.slf4j.Slf4j;
/**
* @Title: MessageAQMonitor.java
* @Description: AQ 监控任务, 在AQ断开后重连
* @author wangqq
* @date 2020年6月28日 下午4:35:31
* @version 1.0
*/
@Slf4j
@Component
public class MessageAQMonitor {
@Autowired
private MessageAQConfig aqConfig;
@Autowired
private MessageAqMapper aqMapper;
@Scheduled(cron = "${message.queue.aq.cron}")
private void monitorJob() {
// 检查消息队列是否启用
if (!aqConfig.enable) {
return;
}
// 获取当前时间,并向前推5分钟
String formatDateTime = DateUtil.formatDateTime(new Date(System.currentTimeMillis() - 300000));
// 将该时间转为0时区的时间【数据库中存储的队列时间为0时区的时间】
String zeroZoneTime = DateUtil.timeConvert(formatDateTime, "+08:00", "+00:00", "yyyy-MM-dd HH:mm:ss");
// 查询是否存在5分钟以前的队列未被消费
int selectCount = aqMapper.selectCount(aqConfig.queue, zeroZoneTime);
if (selectCount != 0) {
// 若存在,则重新启动监听
if (MessageAQConnection.closeConnection()) {
log.info("--> AQ connection has been closed.");
if (MessageAQConnection.establishConnection(aqConfig)) {
log.info("--> AQ connection has been re-established.");
}
}
}
}
}
2.9. 队列表中队列数量的查询
- 根据队列名称和入队时间,查询在入队时间之后入对的队列数量
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
/**
* @Title: MessageAqMapper.java
* @Description: oracleAQ的查询
* @author wangqq
* @date 2020年6月28日 下午4:04:50
* @version 1.0
*/
@Mapper
public interface MessageAqMapper {
/**
*
* 查询数据库中的队列表中符合条件的队列的条数
*
* @param qName 队列名称
* @param minDatetime 队列入队的最小时间
* @return
* @author wangqq
* @date 2020-07-10 15:44:43
*/
@Select("select count(msgid) from T_QUEUE_TABLE t where t.q_name = #{qName,jdbcType=VARCHAR} "
+ "and to_char(cast(t.enq_time AS DATE), 'yyyy-MM-dd HH24:mi:ss') < #{minDatetime,jdbcType=VARCHAR}")
int selectCount(String qName, String minDatetime);
}
JMS监听Oracle AQ的更多相关文章
- Oracle 11g RAC 环境下单实例非缺省监听及端口配置
如果在Oracle 11g RAC环境下使用dbca创建单实例数据库后,Oracle会自动将其注册到缺省的1521端口及监听器.大多数情况下我们使用的为非缺省监听器以及非缺省的监听端口.而且在Orac ...
- ORACLE监听配置及测试实验(2)
实验四 在tnsname.ora里添加默认监听代号 [oracle@oracle01 admin]$ vi tnsnames.ora 添加一行 PORT1528=(ADDRESS = (PROTOCO ...
- 修改oracle默认监听端口
修改oracle默认监听端口 oracle端口修改 主要是修改两个文件和修改oracle参数local_listener 1 查看当前监听状态 [oracle@test ~]$ lsnrctl sta ...
- tomcat监听activemq jms配置
当从webservice接收到信息的时候,消息生产者producer立刻把收到的消息放入到jms里面,消费者cusomer这时要设置一个监听,当生产者发送消息时,只要消息被发出来,消费者就会接收到消息 ...
- centos 安装oracle 11g r2(二)-----监听配置与创建数据库实例
centos 安装oracle 11g r2(二)-----监听配置与创建数据库实例 一.监听配置(命令:netca) 1.以 oracle 用户输入命令,启动图形化工具配置监听 [oracle@lo ...
- Oracle LISTENER 主机名修改为IP地址后LISTENER无法监听到实例 oracle监听错误与hosts文件配置
为什么listener.ora文件里面HOST后面到底应该输入IP地址还是主机名.我的经验告诉我,这边最好使用主机名.很多的时候,一个机器绑定的不只一个IP地址,如HOST后面是IP地址,那么ORAC ...
- oracle创建静态监听
[oracle@localhost admin]$ pwd /u01/app/oracle/product/11.2.0/dbhome_1/network/admin [oracle@localhos ...
- linux 开启oracle监听
secureCRT连接到数据库所在的linux机器,切换到oracle用户模式下 [root@nstlbeta ~]# su - oracle 步骤阅读 2 然后用sqlplus登录到数据库,关闭数据 ...
- ORACLE监听理解
参考官方文档Net Services Reference的7 Oracle Net Listener Parameters (listener.ora) 1 监听概念 oracle监听,是个服务器端进 ...
随机推荐
- 【Go语言绘图】图片的旋转
在上一篇中,我们了解了gg库的基本使用,包括调整大小.调整圆形参数.设置颜色.保存图片.加载图片和裁剪.这一篇我们来学习一下图片的旋转. 加载图片 首先,我们先来一张黄图. func TestRota ...
- Tokyo 五年 IT 生活
今天阳光甚好,在家中小屋,闲来无事,回顾一下这五年的历程.我想从来东京的缘由.东京的环境.生活.IT这四个方面介绍一下. 首先,说一下为什么我会来到东京. 电子信息专业毕业,大学实验室学习IT,毕业后 ...
- 解决Linux所有命令不能使用的问题
解决Linux所有命令不能使用的问题 出现这个问题说明你的 /etc/profile 配置出现了问题,一般是因为path配置出现了问题.排除添加内容中的错误,然后重启一个新窗口执行执行 source ...
- 群晖DS218+部署GitLab
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...
- 学习Python之数据类型
格式化字符串 字符串格式化是一种非常简洁的特性,它能让我们动态更新字符串中的内容.假设我们有从服务器获取的用户信息,并希望根据该信息显示自定义消息,第一个想法是应用字符串连接之类的东西. first_ ...
- Spring Cloud 2020.0.0正式发布,再见了Netflix
目录 ✍前言 版本约定 ✍正文 Spring Cloud版本管理 与Spring Boot版本对应关系 当前支持的版本 阻断式升级(不向下兼容) 1.再见了,Netflix Netflix组件替代方案 ...
- HCIP --- BGP 总结
AS:自治系统 --逻辑管理域(例如移动.电信.联通),AS号范围:0-65535,其中,1-64511:公有AS,64512-65535:私有AS IGP:内部网关协议,在一个AS之内传递的路由协 ...
- Liunx运维(六)-文件备份与压缩命令
文档目录: 一.tar:打包备份 二.gzip:压缩或解压文件 三.zip:打包和压缩文件 四.unzip:解压zip文件 五.scp:远程文件复制 六.rsync:文件同步工具 ---------- ...
- Java基础之String中equals,声明方式,等大总结
无论你是一个编程新手还是老手,提到String你肯定感觉特别熟悉,因为String类我们在学习java基础的时候就已经学过,但是String类型有我们想象的那么简单吗?其实不然,String类型的知识 ...
- AES 逻辑
分组长度 加密逻辑 轮函数 参考:链接 字节代换 两种方法: 1.首先(将字节看做GF(28)上的元素,映射到自己的乘法逆元)换成人话就是(对多项式的逆,参考:链接): 其次,对字节做仿射变换 2 ...