使用 JAXB 将子类实例作为超类传递

发布于 2024-09-25 06:06:29 字数 3823 浏览 1 评论 0原文

我拥有的是一组表示消息类型的 Java 类(接近 25 个)。它们都继承自 Message 类,我希望该类是抽象的。每个消息类型都会向 Message 超类提供的集合中添加一些附加字段。

我正在使用 RESTeasy 实现一些 RESTful Web 服务,并且希望拥有这样的方法:

public Response persist(Message msg) {
    EntityTransaction tx = em.getTransaction();
    tx.begin();
    try {
        em.persist(msg);
    } catch (Exception e) {
        e.printStackTrace();
    }
    tx.commit();
    em.close();
    return Response.created(URI.create("/message/" + msg.getId())).build();
}

而不是使用 25 个单独的持久方法,每个方法都针对特定的消息类型进行定制。

目前,我已经像这样注释了我的 Message 类:

@MappedSuperclass
@XmlRootElement(name = "message")
public abstract class Message implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    Integer id;
    @Embedded
    Header header;
    @Embedded
    SubHeader subHeader;

然后我的子类看起来像这样:

@Entity
@XmlRootElement(name="regmessage")
@XmlAccessorType(XmlAccessType.FIELD)
public class REGMessage extends Message {

    @XmlElement(required = true)
    int statusUpdateRate;
    @XmlElement(required = true)
    int networkRegistrationFlag;

这创建了一个模式,它看起来应该可以工作,但是在持久操作期间在服务器端看到的所有内容都是Message 对象(子类型完全丢失,或者至少没有编组回其正确的子类型)。在客户端,要调用我这样做的方法:

REGMessage msg = new REGMessage();
// populate its fields
Response r = client.createMessage(msg);

我正在尝试的可能吗?我需要使用什么 JAXB 魔法才能使翻译按应有的方式进行——即,将 Java 中的所有内容都视为一条消息,以减少方法数量,同时仍保留所有特定于子类型的信息?


感谢 Blaise 的博客指点,现在看起来它正在全面发挥作用。这就是我所得到的,它确实有效:

//JAXB annotations
@XmlRootElement(name="message")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlSeeAlso(REGMessage.class)
//JPA annotations
@MappedSuperclass
public class Message {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @XmlAttribute
    private Integer id;

    private JICDHeader header;
    private int subheader;

    @XmlAnyElement
    @Transient
    private Object body;

我今天早上遇到的问题之一是 Hibernate 的一个关于列数不匹配的神秘错误。当我意识到“body”被映射到表中时,我将其标记为瞬态,瞧!

@XmlRootElement(name="regmessage")
@XmlAccessorType(XmlAccessType.FIELD)
@Entity
public class REGMessage extends Message {

    private int field1;
    private int field2;

现在从此代码生成的唯一表是 regmessage 表。在 RESTeasy 方面:

@Path("/messages")
public class MessageResource implements IMessageResource {

    private EntityManagerFactory emf;
    private EntityManager em;

    Logger logger = LoggerFactory.getLogger(MessageResource.class);

    public MessageResource() {
        try {
            emf = Persistence.createEntityManagerFactory("shepherd");
            em = emf.createEntityManager();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    @POST
    @Consumes("application/xml")
    public Response saveMessage(Message msg) {

        System.out.println(msg.toString());

        logger.info("starting saveMessage");
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {
            em.persist(msg);
        } catch (Exception e) {
            e.printStackTrace();
        }

        tx.commit();
        em.close();
        logger.info("ending saveMessage");

        return Response.created(URI.create("/message/" + msg.getId())).build();
    }
}

这实现了一个接口:

@Path("/messages")
public interface IMessageResource {

    @GET
    @Produces("application/xml")
    @Path("{id}")
    public Message getMessage(@PathParam("id") int id);

    @POST
    @Consumes("application/xml")
    public Response saveMessage(Message msg) throws URISyntaxException;

}

Marshalling &解组工作按预期进行,并且持久化是子类的表(并且根本没有超类表)。

我确实看到了 Blaise 关于 JTA 的注释,在我完成消息和消息的充实之后,我可能会尝试将其纳入此组合中。 REGMessage 类完全退出。

What I have is a set of Java classes (close to 25) representing message types. They all inherit from a Message class which I'd like to be abstract. Each message type adds a few additional fields to the set provided by the Message superclass.

I'm implementing some RESTful web services using RESTeasy and would like to have methods like this:

public Response persist(Message msg) {
    EntityTransaction tx = em.getTransaction();
    tx.begin();
    try {
        em.persist(msg);
    } catch (Exception e) {
        e.printStackTrace();
    }
    tx.commit();
    em.close();
    return Response.created(URI.create("/message/" + msg.getId())).build();
}

instead of having 25 separate persist methods, each tailored to a particular message type.

Currently, I've annotated my Message class like this:

@MappedSuperclass
@XmlRootElement(name = "message")
public abstract class Message implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    Integer id;
    @Embedded
    Header header;
    @Embedded
    SubHeader subHeader;

My subclass then looks like this:

@Entity
@XmlRootElement(name="regmessage")
@XmlAccessorType(XmlAccessType.FIELD)
public class REGMessage extends Message {

    @XmlElement(required = true)
    int statusUpdateRate;
    @XmlElement(required = true)
    int networkRegistrationFlag;

This creates a schema which looks like it should work, but all that's seen on the server side during a persist operation is a Message object (the subtype is completely lost, or at least it isn't marshalled back into its proper subtype). On the client side, to invoke the method I do this:

REGMessage msg = new REGMessage();
// populate its fields
Response r = client.createMessage(msg);

Is what I'm attempting possible? What JAXB magic do I need to use to make the translations happen the way they should -- ie, to treat everything in Java as if it's a Message to keep the number of methods down yet still preserve all the subtype-specific information?


Thanks to Blaise's blog pointers, this now looks like it's on the road to working fully. Here's what I've got, and it does work:

//JAXB annotations
@XmlRootElement(name="message")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlSeeAlso(REGMessage.class)
//JPA annotations
@MappedSuperclass
public class Message {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @XmlAttribute
    private Integer id;

    private JICDHeader header;
    private int subheader;

    @XmlAnyElement
    @Transient
    private Object body;

One of the problems I encountered this morning was a cryptic error from Hibernate about the number of columns being mismatched. Once I realized that "body" was being mapped into the table, I marked it transient and voila!

@XmlRootElement(name="regmessage")
@XmlAccessorType(XmlAccessType.FIELD)
@Entity
public class REGMessage extends Message {

    private int field1;
    private int field2;

The only table generated from this code now is the regmessage table. On the RESTeasy side:

@Path("/messages")
public class MessageResource implements IMessageResource {

    private EntityManagerFactory emf;
    private EntityManager em;

    Logger logger = LoggerFactory.getLogger(MessageResource.class);

    public MessageResource() {
        try {
            emf = Persistence.createEntityManagerFactory("shepherd");
            em = emf.createEntityManager();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    @POST
    @Consumes("application/xml")
    public Response saveMessage(Message msg) {

        System.out.println(msg.toString());

        logger.info("starting saveMessage");
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        try {
            em.persist(msg);
        } catch (Exception e) {
            e.printStackTrace();
        }

        tx.commit();
        em.close();
        logger.info("ending saveMessage");

        return Response.created(URI.create("/message/" + msg.getId())).build();
    }
}

This implements an interface:

@Path("/messages")
public interface IMessageResource {

    @GET
    @Produces("application/xml")
    @Path("{id}")
    public Message getMessage(@PathParam("id") int id);

    @POST
    @Consumes("application/xml")
    public Response saveMessage(Message msg) throws URISyntaxException;

}

Marshalling & unmarshalling work as expected, and persistence is to the subclass's table (and there is no superclass table at all).

I did see Blaise's note about JTA, which I may attempt to bring into this mix after I finish fleshing the Message & REGMessage classes back out fully.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

仅此而已 2024-10-02 06:06:29

您是否尝试过将以下内容添加到您的消息类中? @XmlSeeAlso 注释将使 JAXBContext 了解子类。

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;

@XmlRootElement
@XmlSeeAlso(RegMessage.class)
public abstract class Message {

    Integer id;

}

替代策略:

以下是我帮助人们使用的策略的链接:

本质上,您有一个消息对象和多个单独的消息有效负载。消息和有效负载之间的关系通过 @XmlAnyElement 注释进行处理。

交易处理注意事项

我注意到您正在处理自己的交易。您是否考虑过将 JAX-RS 服务实现为会话 bean 并利用 JTA 进行事务处理?有关示例,请参阅:

Have you tried adding the following to your message class? The @XmlSeeAlso annotation will let the JAXBContext know about the subclasses.

import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlSeeAlso;

@XmlRootElement
@XmlSeeAlso(RegMessage.class)
public abstract class Message {

    Integer id;

}

Alternate Strategy:

Here is a link to a strategy I have helped people use:

Essentially you have one message object, and multiple individual message payloads. The relationship between the message and payload is handled through a @XmlAnyElement annotation.

Note on Transaction Handling

I noticed that you are handling your own transactions. Have you considered implementing your JAX-RS service as a session bean and leverage JTA for your transaction handling? For an example see:

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文