问题描述
我拥有的是一组代表消息类型的 Java 类(接近 25 个).它们都继承自我想抽象的 Message 类.每种消息类型都会向 Message 超类提供的集合添加一些额外的字段.
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.
我正在使用 RESTeasy 实现一些 RESTful Web 服务,并希望有这样的方法:
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(); }
而不是拥有 25 个单独的持久化方法,每个方法都针对特定的消息类型量身定制.
instead of having 25 separate persist methods, each tailored to a particular message type.
目前,我已经像这样注释了我的 Message 类:
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;
这会创建一个 看起来 应该可以工作的架构,但是在持久化操作期间在服务器端看到的只是一个 Message 对象(子类型完全丢失,或者至少它没有)t 编组回其正确的子类型).在客户端,要调用我这样做的方法:
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);
我正在尝试的可能吗?我需要使用什么 JAXB 魔法来使翻译以它们应有的方式发生 - 即,将 Java 中的所有内容都视为一条消息,以减少方法数量但仍保留所有特定于子类型的信息?
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?
感谢 Blaise 的博客指点,现在看起来它正在全面运行.这是我所拥有的,它确实有效:
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;
我今天早上遇到的一个问题是来自 Hibernate 的关于列数不匹配的神秘错误.一旦我意识到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;
现在唯一从此代码生成的表是 regmessage 表.在 RESTeasy 方面:
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(); } }
这实现了一个接口:
@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).
我确实看到了 Blaise 关于 JTA 的说明,在我完成 Message ®Message 类完全退出.
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.
推荐答案
您是否尝试将以下内容添加到您的消息类中?@XmlSeeAlso 注解会让 JAXBContext 知道子类.
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; }
替代策略:
这是我帮助人们使用的策略的链接:
Here is a link to a strategy I have helped people use:
- http://bdoughan.blogspot.com/2010/08/using-xmlanyelement-to-build-generic.html
基本上你有一个消息对象和多个单独的消息有效负载.消息和有效负载之间的关系通过@XmlAnyElement 注解处理.
Essentially you have one message object, and multiple individual message payloads. The relationship between the message and payload is handled through a @XmlAnyElement annotation.
事务处理注意事项
我注意到您正在处理自己的交易.您是否考虑过将 JAX-RS 服务实现为会话 bean 并利用 JTA 进行事务处理?示例见:
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:
- http://bdoughan.blogspot.com/2010/08/creating-restful-web-service-part-45.html