JMS规范以及JMS的可靠性(代码演示持久性、事务以及签收)

JMS规范

1.什么是JMS

Java Message Service(Java消息服务是JavaEE中的一个技术)

Java消息服务指的是两个应用程序之间进行异步通信的API,它为标准消息协议和消息服务提供了一组通用接口,包括创建、发送、读取消息等,用于支持JAVA应用程序开发。在JavaEE中,当两个应用程序使用JMS进行通信时,它们之间并不是直接相连的,而是通过一个共同的消息收发服务组件关联起来以达到解耦/异步削峰的效果。

2.JMS的组成结构和特点

在这里插入图片描述

JMS如上图由四部分组成

JMS provider

实现JMS接口和规范的消息中间件,也就是我们的MQ服务器

JMS producer

消息生产者,创建和发送JMS消息的客户端应用

JMS consumer

消息消费者,接收和处理JMS消息的客户端应用

JMS message

消息头、消息体、消息属性。

消息头

JMSDestination:消息发送的目的地,主要是指Queue和Topic
JMSDeliveryMode:持久模式和非持久模式。

  • 一条持久性的消息:应该被传送“一次仅仅一次”,这就意味者如果JMS提供者出现故障,该消息并不会丢失,它会在服务器恢复之后再次传递。
  • 一条非持久的消息:最多会传送一次,这意味这服务器出现故障,该消息将永远丢失。

JMSExpiration:可以设置消息在一定时间后过期,默认是永不过期

  • 消息过期时间,等于Destination的send 方法中的timeToLive值加上发送时刻的GMT 时间值。
  • 如果timeToLive 值等于零,则JMSExpiration 被设为零,表示该消息永不过期。
  • 如果发送后,在消息过期时间之后消息还没有被发送到目的地,则该消息被清除。

JMSPriority:消息优先级,从 0-9 十个级别,0到4是普通消息,5到9是加急消息。

  • JMS不要求MQ严格按照这十个优先级发送消息,但必须保证加急消息要先于普通消息到达。默认是4级。

JMSMessageID:唯一识别每个消息的标识由MQ产生。

消息体
在这里插入图片描述

消息属性

1.如果需要除消息头字段以外的值,那么可以使用消息属性。识别/去重/重点标注等操作非常有用的方法

2.他们是以属性名和属性值对的形式制定的。可以将属性是为消息头得扩展,属性指定一些消息头没有包括的附加信息,比如可以在属性里指定消息选择器。

3.消息的属性就像可以分配给一条消息的附加消息头一样。它们允许开发者添加有关消息的不透明附加信息。它们还用于暴露消息选择器在消息过滤时使用的数据。

TextMessage message = session.createTextMessage();
message.setText(text);
message.setStringProperty("username","z3"); //自定义属性

3.JMS开发的基本步骤

在这里插入图片描述

JMS开发的基本步骤

1:创建一个connection factory
2:通过connection factory来创建JMS connection
3:启动JMS connection
4:通过connection创建JMS session
5:创建JMS destination
6:创建JMS producer或者创建JMS message并设置destination
7:创建JMS consumer或者是注册一个JMS message listener
8:发送或者接受JMS message(s)
9:关闭所有的JMS资源(connection, session, producer, consumer等)

代码案例(点对点消息传递):

JMS provider:ActiveMQ

JMS producer

/*
 *@auther xiaoshenlong
 *@create 2019-11-05 14:30
 *生产者向消息队列存入消息
 */
public class JMSProduce
{
    public static final String ACTIVEMQ_URL = "tcp://192.168.1.135:61616";
    public static final String QUEUE_NAME = "myQueue-01";
    public static void main(String[] args) throws JMSException
    {
        //1 创建连接工场,使用默认用户名密码,编码不再体现
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2 获得连接并启动
        Connection connection = activeMQConnectionFactory.createConnection();
        connection.start();
        //3 创建会话,此步骤有两个参数,第一个是否以事务的方式提交,第二个默认的签收方式
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //4 创建队列
        Queue queue = session.createQueue(QUEUE_NAME);
        //5 创建生产者
        MessageProducer messageProducer = session.createProducer(queue);
        for(int i=1; i<=6; i++)//生产6条消息给MQ
        {
            //6 创建消息
            TextMessage message = session.createTextMessage("message--"+i);

            //7 通过消息生产者发布消息
            messageProducer.send(message);
            
            //消息体实现(可以是五种消息体格式中的任意一种)
            MapMessage mapMessage = session.createMapMessage();
            mapMessage.setString("k1","queue-map-message-"+i);
            messageProducer.send(mapMessage);
            
        }
        //8 关闭资源
        messageProducer.close();
        session.close();
        connection.close();

        System.out.println("**********消息发送到MQ完毕");
    }
}

JMS consumer

/**
*@auther xiaoshenlong
*@create 2019-11-05 15:26
*/
public class JMSConsumer
{
    public static final String ACTIVEMQ_URL = "tcp://192.168.1.135:61616";
    public static final String QUEUE_NAME = "myQueue-01";

    public static void main(String[] args) throws JMSException, InterruptedException {
        //1 创建连接工场,使用默认用户名密码,编码不再体现
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2 获得连接并启动
        Connection connection = activeMQConnectionFactory.createConnection();
        connection.start();
        //3 创建会话,此步骤有两个参数,第一个是否以事务的方式提交,第二个默认的签收方式
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //4 创建队列
        Queue queue = session.createQueue(QUEUE_NAME);
        //5 创建消费者
        MessageConsumer messageConsumer = session.createConsumer(queue);
        //6 用来接收消息(创建消息的监听)
        messageConsumer.setMessageListener(new MessageListener() {
            @Override
            public void onMessage(Message message) {
                if(message instanceof TextMessage){
                    try {
                        String text = ((TextMessage) message).getText();
                        System.out.println("*****接收到MQ队列消息:"+text);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                //接收Map类型的消息
                if(message instanceof MapMessage){
                    try {
                        MapMessage mapMessage = (MapMessage)message;
                        String text = mapMessage.getString("k1");
                        System.out.println("*****接收到MQ队列Map消息:"+text);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        });
            Thread.sleep(Long.MAX_VALUE);
        //7 关闭
        messageConsumer.close();
        session.close();
        connection.close();
    }
}

JMS message 消息头的设置:
在这里插入图片描述
除了在步骤六创建消息处设置消息头之外还可以在send中设置
在这里插入图片描述
在这里插入图片描述
消息体设置
在这里插入图片描述

4.JMS的可靠性

案例均以发布/订阅消息传递的方式演示不再演示点对点消息传递的方式(点对点消息传递的方式默认持久化,两种方式不清楚的参见我博客)

4.1 PERSISTENT:持久性

在这里插入图片描述

案例演示:

点对点消息传递模式持久性案例演示

设置持久化方式(非持久)

messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);

JMSProduce_queue:

/*
 *@auther xiaoshenlong
 *@create 2019-11-05 14:30
 *生产者向消息队列存入消息
 */
public class JMSProduce_queue
{
    public static final String ACTIVEMQ_URL = "tcp://192.168.1.135:61616";
    public static final String QUEUE_NAME = "myQueue-01";
    public static void main(String[] args) throws JMSException
    {
        //1 创建连接工场,使用默认用户名密码,编码不再体现
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2 获得连接并启动
        Connection connection = activeMQConnectionFactory.createConnection();
        connection.start();
        //3 创建会话,此步骤有两个参数,第一个是否以事务的方式提交,第二个默认的签收方式
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //4 创建队列
        Queue queue = session.createQueue(QUEUE_NAME);
        //5 创建生产者
        MessageProducer messageProducer = session.createProducer(queue);
        //设置持久化方式(非持久)
        messageProducer.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
        for(int i=1; i<=3; i++)//生产3条消息给MQ
        {
            //6 创建消息
            TextMessage message = session.createTextMessage("message--"+i);
            //7 通过消息生产者发布消息
            messageProducer.send(message);
        }
        //8 关闭资源
        messageProducer.close();
        session.close();
        connection.close();

        System.out.println("**********消息发送到MQ完毕");
    }
}

JMSConsumer_queue:

/**
 *@auther xiaoshenlong
 *@create 2019-11-05 15:26
 */
public class JMSConsumer_queue
{
    public static final String ACTIVEMQ_URL = "tcp://192.168.1.135:61616";
    public static final String QUEUE_NAME = "myQueue-01";

    public static void main(String[] args) throws JMSException, InterruptedException {
        //1 创建连接工场,使用默认用户名密码,编码不再体现
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2 获得连接并启动
        Connection connection = activeMQConnectionFactory.createConnection();
        connection.start();
        //3 创建会话,此步骤有两个参数,第一个是否以事务的方式提交,第二个默认的签收方式
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //4 创建队列
        Queue queue = session.createQueue(QUEUE_NAME);
        //5 创建消费者
        MessageConsumer messageConsumer = session.createConsumer(queue);
        //6 用来接收消息(创建消息的监听)
        messageConsumer.setMessageListener(new MessageListener() {
            @Override
            public void onMessage(Message message) {
                if(message instanceof TextMessage){
                    try {
                        String text = ((TextMessage) message).getText();
                        System.out.println("*****接收到MQ队列消息:"+text);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                if(message instanceof MapMessage){
                    try {
                        MapMessage mapMessage = (MapMessage)message;
                        String text = mapMessage.getString("k1");
                        System.out.println("*****接收到MQ队列消息:"+text);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        Thread.sleep(Long.MAX_VALUE);
        //7 关闭
        messageConsumer.close();
        session.close();
        connection.close();
    }
}

启动生产者JMSProduce_queue观察ActiveMQ控制台
在这里插入图片描述

重启ActiveMQ服务模拟服务器宕机后恢复
在这里插入图片描述
观察ActiveMQ控制台:(宕机前生产的消息已经消失)

在这里插入图片描述

启动消费者JMSConsumer_queue:(无法获取消息进行消费)
在这里插入图片描述
设置持久化方式(持久)

messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);

JMSProduce_queue:

/*
 *@auther xiaoshenlong
 *@create 2019-11-05 14:30
 *生产者向消息队列存入消息
 */
public class JMSProduce_queue
{
    public static final String ACTIVEMQ_URL = "tcp://192.168.1.135:61616";
    public static final String QUEUE_NAME = "myQueue-01";
    public static void main(String[] args) throws JMSException
    {
        //1 创建连接工场,使用默认用户名密码,编码不再体现
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2 获得连接并启动
        Connection connection = activeMQConnectionFactory.createConnection();
        connection.start();
        //3 创建会话,此步骤有两个参数,第一个是否以事务的方式提交,第二个默认的签收方式
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //4 创建队列
        Queue queue = session.createQueue(QUEUE_NAME);
        //5 创建生产者
        MessageProducer messageProducer = session.createProducer(queue);
        //设置持久化方式(持久)
        messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
        for(int i=1; i<=3; i++)//生产3条消息给MQ
        {
            //6 创建消息
            TextMessage message = session.createTextMessage("message--"+i);
            //7 通过消息生产者发布消息
            messageProducer.send(message);
        }
        //8 关闭资源
        messageProducer.close();
        session.close();
        connection.close();

        System.out.println("**********消息发送到MQ完毕");
    }

}

JMSConsumer_queue:

/**
 *@auther xiaoshenlong
 *@create 2019-11-05 15:26
 */
public class JMSConsumer_queue
{
    public static final String ACTIVEMQ_URL = "tcp://192.168.1.135:61616";
    public static final String QUEUE_NAME = "myQueue-01";
    public static void main(String[] args) throws JMSException, InterruptedException {
        //1 创建连接工场,使用默认用户名密码,编码不再体现
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2 获得连接并启动
        Connection connection = activeMQConnectionFactory.createConnection();
        connection.start();
        //3 创建会话,此步骤有两个参数,第一个是否以事务的方式提交,第二个默认的签收方式
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //4 创建队列
        Queue queue = session.createQueue(QUEUE_NAME);
        //5 创建消费者
        MessageConsumer messageConsumer = session.createConsumer(queue);
        //6 用来接收消息(创建消息的监听)
        messageConsumer.setMessageListener(new MessageListener() {
            @Override
            public void onMessage(Message message) {
                if(message instanceof TextMessage){
                    try {
                        String text = ((TextMessage) message).getText();
                        System.out.println("*****接收到MQ队列消息:"+text);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                if(message instanceof MapMessage){
                    try {
                        MapMessage mapMessage = (MapMessage)message;
                        String text = mapMessage.getString("k1");
                        System.out.println("*****接收到MQ队列消息:"+text);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        Thread.sleep(Long.MAX_VALUE);
        //7 关闭
        messageConsumer.close();
        session.close();
        connection.close();
    }
}

启动生产者JMSProduce_queue观察ActiveMQ控制台
在这里插入图片描述

重启ActiveMQ服务模拟服务器宕机后恢复
在这里插入图片描述
观察ActiveMQ控制台:(宕机前由于持久化设置生产的消息已经被存入文件中)
在这里插入图片描述
启动消费者JMSConsumer_queue:(持久化到文件中的消息被消费)
在这里插入图片描述
在这里插入图片描述

发布/订阅消息传递模式持久性案例演示

JMSProduce_topic:(发布/订阅消息传递模式需要先设置持久化之后再开启连接)

/*
 *@auther xiaoshenlong
 *@create 2019-11-05 14:30
 *生产者向消息队列存入消息
 */
public class JMSProduce_topic
{
    public static final String ACTIVEMQ_URL = "tcp://192.168.1.135:61616";
    public static final String TOPIC_NAME = "myTopic-01";
    public static void main(String[] args) throws JMSException
    {
        //1 创建连接工场,使用默认用户名密码,编码不再体现
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2 获得连接
        Connection connection = activeMQConnectionFactory.createConnection();
        //3 创建会话,此步骤有两个参数,第一个是否以事务的方式提交,第二个默认的签收方式
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //4 创建队列
        Topic topic = session.createTopic(TOPIC_NAME);
        //5 创建生产者
        MessageProducer messageProducer = session.createProducer(topic);
        //开启持久化
        messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);
        //启动连接应该放在持久后之后开启
        connection.start();
        for(int i=1; i<=6; i++)//生产6条消息给MQ
        {
            //6 创建消息
            TextMessage message = session.createTextMessage("Topic--message--"+i);
            //7 通过消息生产者发布消息
            messageProducer.send(message);
        }
        //8 关闭资源
        messageProducer.close();
        session.close();
        connection.close();

        System.out.println("**********发送持久化主题消息到MQ完毕");
    }
}

JMSConsumer_topic:

/**
 *@auther xiaoshenlong
 *@create 2019-11-05 15:26
 */
public class JMSConsumer_topic
{
    public static final String ACTIVEMQ_URL = "tcp://192.168.1.135:61616";
    public static final String TOPIC_NAME = "myTopic-01";

    public static void main(String[] args) throws JMSException, InterruptedException {
        //1 创建连接工场,使用默认用户名密码,编码不再体现
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2 获得连接
        Connection connection = activeMQConnectionFactory.createConnection();
        //2.1设置订阅用户
        connection.setClientID("xiaoshenlong");
        //3 创建会话,此步骤有两个参数,第一个是否以事务的方式提交,第二个默认的签收方式
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //4 创建主题
        Topic topic = session.createTopic(TOPIC_NAME);
        //5 创建订阅者
        TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic,"topicSubscriber");
        //6 启动连接
        connection.start();
        //7 用来接收消息(同步阻塞方式)
        Message message = topicSubscriber.receive();
        
            while(null!=message){
                TextMessage textMessage = (TextMessage)message;
                String text = textMessage.getText();
                System.out.println("*****接收到MQ持久主题消息:"+text);
                message = topicSubscriber.receive(1000L);
            }

        //7 关闭
        topicSubscriber.close();
        session.close();
        connection.close();
    }
}

先启动订阅者,即JMSConsumer_topic,并查看控制台
在这里插入图片描述

再开启订阅消息发布者JMSProduce_topic,查看订阅者是否收到订阅消息
在这里插入图片描述
在这里插入图片描述
再次查看控制台:
在这里插入图片描述

追加测试

我们知道,发布/订阅消息域这种方式的特点是生产者和消费者之间有时间上的相关性。订阅某一个主题的消费者只能消费自它订阅之后发布的消息。生产者生产时,topic不保存消息它是无状态的不落地,假如无人订阅就去生产,那就是一条废消息,所以,一般先启动消费者再启动生产者

那当我们开启了持久化后,先启动生产者而后再启动消费者,能不能接收到消息呢?

启动JMSProduce_topic
在这里插入图片描述
启动JMSConsumer_topic

在这里插入图片描述
查看控制台
在这里插入图片描述
结论

发布/订阅模式开启持久化后,即使先启动生产者再启动消费者(订阅者)也不会像未开启持久化那样产生废消息,无关启动顺序,只要发布消息,订阅者何时启动都可接收到发布的消息。(类似于用户关注了微信公众号,不论用户在公众号发布推文的当前时间是否在线,之后用户只要上线就能接收到并查看之前公众号发布的推文)

服务器宕机测试

当我们先启动JMSProduce_topic发布订阅生产者后,关闭ActiveMQ消息队列中间件使服务宕机,而后再次启动,那么开启了持久化的消息是否还存在在消息队列中,订阅消息消费者JMSConsumer_topic能否继续接收到消息呢?

启动JMSProduce_topic
在这里插入图片描述

查看控制台
在这里插入图片描述

手动关闭ActiveMQ服务模拟服务器宕机,并重新启动
在这里插入图片描述

查看控制台:发现消息队列中等待接收的消息为0
在这里插入图片描述

启动消息订阅者JMSConsumer_topic:

在这里插入图片描述

JMS持久化总结:

点对点消息传递模式

点对点消息传递模式中默认开启持久化,即使不设置持久化参数messageProducer.setDeliveryMode(DeliveryMode.PERSISTENT);也是开启状态,即在生产者生产完消息后服务器意外宕机重启服务器后,开启消费者仍然能接收到消息,因为开启持久化参数后已经将消息写入文件中。这是队列的的默认传送模式,此模式保证这些消息只被传送一次和成功使用一次。对于这些消息,可靠性是优先考虑的因素。可靠性的另一个重要方面是确保持久性消息传送至目标后,消息服务在向消费者传送它们之前不会丢失这些消息。

发布/订阅消息模式

发布/订阅消息传递模式中开启持久化后,即使在生产者先启动情况下,也不会产生废消息,后启动的消费者仍然能够接收到消息,即使生产者生产完消息服务器意外宕机,重启服务器后消费者也依旧可以收到消息。

4.2 Transaction:事务

在这里插入图片描述

案例演示:

点对点消息传递模式事务案例演示

未开启事务

Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);

JMSProduce_queue:(生产者)

/*
 *@auther xiaoshenlong
 *@create 2019-11-05 14:30
 *生产者向消息队列存入消息
 */
public class JMSProduce_queue
{
    public static final String ACTIVEMQ_URL = "tcp://192.168.1.135:61616";
    public static final String QUEUE_NAME = "myQueue-01";
    public static void main(String[] args) throws JMSException
    {
        //1 创建连接工场,使用默认用户名密码,编码不再体现
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2 获得连接并启动
        Connection connection = activeMQConnectionFactory.createConnection();
        connection.start();
        //3 创建会话,此步骤有两个参数,第一个是否以事务的方式提交,第二个默认的签收方式
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //4 创建队列
        Queue queue = session.createQueue(QUEUE_NAME);
        //5 创建生产者
        MessageProducer messageProducer = session.createProducer(queue);
        for(int i=1; i<=3; i++)//生产3条消息给MQ
        {
            //6 创建消息
            TextMessage message = session.createTextMessage("message--"+i);
            //7 通过消息生产者发布消息
            messageProducer.send(message);
        }
        //8 关闭资源
        messageProducer.close();
        session.close();
        connection.close();

        System.out.println("**********消息发送到MQ完毕");
    }
}

启动生产者JMSProduce_queue,观察ActiveMQ控制台
在这里插入图片描述
生产者开启事务

Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);

JMSProduce_queue:

/*
 *@auther xiaoshenlong
 *@create 2019-11-05 14:30
 *生产者向消息队列存入消息
 */
public class JMSProduce_queue
{
    public static final String ACTIVEMQ_URL = "tcp://192.168.1.135:61616";
    public static final String QUEUE_NAME = "myQueue-01";
    public static void main(String[] args) throws JMSException
    {
        //1 创建连接工场,使用默认用户名密码,编码不再体现
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2 获得连接并启动
        Connection connection = activeMQConnectionFactory.createConnection();
        connection.start();
        //3 创建会话,此步骤有两个参数,第一个是否以事务的方式提交,第二个默认的签收方式
        Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
        //4 创建队列
        Queue queue = session.createQueue(QUEUE_NAME);
        //5 创建生产者
        MessageProducer messageProducer = session.createProducer(queue);
        for(int i=1; i<=3; i++)//生产3条消息给MQ
        {
            //6 创建消息
            TextMessage message = session.createTextMessage("message--"+i);
            //7 通过消息生产者发布消息
            messageProducer.send(message);
        }
        //8 关闭资源
        messageProducer.close();
        session.close();
        connection.close();
        System.out.println("**********消息发送到MQ完毕");
    }
}

启动生产者JMSProduce_queue,观察ActiveMQ控制台:(启动了生产者却发现队列中没有消息)
在这里插入图片描述

原因:由于开启了事务,所以生产者生产完消息后需要提交(commit)操作

在这里插入图片描述
设置session.commit提交事务后再次启动生产者查看控制台
在这里插入图片描述

JMSConsumer_queue:(消费者开启事务也需要进行session.commit提交)

/**
 *@auther xiaoshenlong
 *@create 2019-11-05 15:26
 */
public class JMSConsumer_topic
{
    public static final String ACTIVEMQ_URL = "tcp://192.168.1.135:61616";
    public static final String TOPIC_NAME = "myTopic-01";
    public static void main(String[] args) throws JMSException, InterruptedException {
        //1 创建连接工场,使用默认用户名密码,编码不再体现
        ActiveMQConnectionFactory activeMQConnectionFactory = new ActiveMQConnectionFactory(ACTIVEMQ_URL);
        //2 获得连接
        Connection connection = activeMQConnectionFactory.createConnection();
        //2.1设置订阅用户
        connection.setClientID("xiaoshenlong");
        //3 创建会话,此步骤有两个参数,第一个是否以事务的方式提交,第二个默认的签收方式
        Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
        //4 创建主题
        Topic topic = session.createTopic(TOPIC_NAME);
        //5 创建订阅者
        TopicSubscriber topicSubscriber = session.createDurableSubscriber(topic,"topicSubscriber");
        //6 启动连接
        connection.start();
        //7 用来接收消息(同步阻塞方式)
        Message message = topicSubscriber.receive();

        while(null!=message){
            TextMessage textMessage = (TextMessage)message;
            String text = textMessage.getText();
            System.out.println("*****接收到MQ持久主题消息:"+text);
            message = topicSubscriber.receive(1000L);
        }

        //7 关闭
        topicSubscriber.close();
        session.commit();
        session.close();
        connection.close();
    }
}

启动消费者并查看ActiveMQ控制台
在这里插入图片描述

在这里插入图片描述

再次启动消费者查看能否进行消费:(由于队列中已无消息无法进行消费)
在这里插入图片描述

JMS事务总结:

1.不论是点对点消息传递模式还是发布/订阅消息传递模式,生产者和消费者开启事务后,都需要进行session.commit进行事务的提交,否则消息队列无法接收到生产者生产的消息。

2.如果只有生产者开启了事务,而消费者没有开启事务,消费者仍然可以获取到并对消息进行消费。

3.如果生产者和消费者均开启了事务,但是消费者对事务没有进行提交,那么就会造成消费者可以重复消费的情况发生。

4.3 Acknowledge:签收

针对消费者

签收方式

  • Session.AUTO_ACKNOWLEDGE(自动签收)

  • Session.CLIENT_ACKNOWLEDGE(手动签收)

    • 需要message.acknowledge();进行签收才能消费消息
    • 若设置手动签收并与此同时开启了事务,在没有设置session.commit提交事务单单只message.acknowledge();确认签收的情况下,消费者仍然能重复消费消息
    • 若设置手动签收并与此同时开启了事务,在设置了session.comm提交事务没有设置message.acknowledge();确认签收的情况下,消费者不能重复消费消息
    • 因此事务的优先级>签收的优先级
  • Session.DUPS_OK_ACKNOWLEDGE(允许重复消息)

事务与签收的关系:

1.生产事务开启,不论签收方式设置为何种模式,都只有commit后才能将全部消息变为已消费。

2.在事务性会话中,当一个事务被成功提交则消息被自动签收。如果事务回滚,则消息会被再次传送。

3.非事务性会话中,消息何时被确认取决于创建会话时的应答模式(acknowledgement mode)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值