@Transient 注解使用

【引言】

在开发过程中,不论是前后端的交互,还是后端与后端间的接口对接,都避免不了各种参数和返回值的问题。有时候需要在返回值的某个实体上增加个其他表的字段,有时候需要在参数传递过程中加上其他表的相关信息。

举个例子,很简单的一个场景,我们在给前端返回订单信息中,前端展示的不仅仅是订单中的信息,也可能需要客户或者商户的一些信息,而这些信息在订单中我们一般只会存一个客户和商户的id,在查询过程中,再去客户和商户表中查询类似名称等信息。而这一两个字段,我们怎么在订单实体的基础上给前端返回去呢?

【方案】

  1. 构造DTO对象

每一个项目中,应该都会有不少DTO对象,一般都是作为交互的传输对象,也是为了方便参数和返回值的调整。

针对上面的场景,我们可以想到构造一个OrderInfoDTO,将订单实体和需要的其它字段作为一个DTO,伪代码如下:

public class OrderInfoDTO {

	/**
     * 订单实体
     */
	private Order order; 
	
	/**
     * 客户名称
     */
	private String customerTitle;

	/**
     * 商户名称
     */
	private String companyName;

}
  1. 使用@Transient注解

不可能在项目中对于每个接口我们都会去构造DTO,或者对于接口的一些调整,我们就需要整个调整。这时候,使用@Transient注解,就可以很简单地处理问题了。伪代码如下:

/**
 * 订单实体
 */
@Data
@Entity
@Table(name = "wms_orders")
public class WmsOrder implements Serializable {
	/**
	 * 订单号
	 */
	private String order_sn; 
	
	/**
	 * 母订单号
	 */
    private String parent_order_sn; 

	/**
	 * 下单时间
	 */
    private Date create_time; 
    
	......
	//以上都是订单表中的字段
	
	//以下是需要通过查询其它表得到的字段信息
	/**
	 * 客户名称
	 */
    @Transient
    private String customerTitle; 

	/**
	 * 商户名称
	 */
    @Transient
    private String companyName; 
	
}

查询订单伪代码:

//1. 查询订单列表
List<Order> orderList = ordersService.findAllOrders(pageNo, pageSize);
//2. 循环订单结果
if (orderList != null && orderList.size() > 0) {
    for (Order orderInfo : orderList) {
        //3.  根据客户id查询对应客户信息,
        Long customerId = orderInfo.getCustomerId();
        if (customerId != null) {
            Customer customerInfo = customerService.get(customerId);
            if (customerInfo != null) {
           		 //customerTitle属性在Order实体中定义,并加了@Transient注解
                orderInfo.setCustomerTitle(customerInfo.getCustomerTitle());
            }
        }
		//4.  根据商户id查询对应商户信息,
        Long companyId = orderInfo.getCompanyId();
        if (companyId != null) {
            Company companyInfo = companyService.get(companyId );
            if (companyInfo != null) {
            	// companyName属性在Order实体中定义,并加了@Transient注解
                orderInfo.setCompanyName(companyInfo.getCompanyName());
            }
        }
    }
}

【介绍】

1. 含义:

@Target(value={METHOD,FIELD})
@Retention(value=RUNTIME)
public @interface Transient

This annotation specifies that the property or field is not persistent. It is used to annotate a property or field of an entity class, mapped superclass, or embeddable class.

此注解是指定属性货字段不持久化的。它可用于实体类,映射超类或可嵌入类的属性或字段上。

简单的说,就是在给某个javabean上需要添加个属性,但是这个属性你又不希望给存到数据库中去,仅仅是做个临时变量,用一下。不修改已经存在数据库的数据的数据结构。

2. 示例:

在上面用伪代码实现了一种场景的使用,下面再分享一个项目中使用的场景:

还是订单,我们系统的订单,是需要分发的,通过mq消费实现。而在这过程中,就需要将订单和订单商品信息都传入到消息中,从而使得消费者可以取到完整的订单。

这一过程中,我们还是将订单实体Order作为消息传递的主实体,利用@Transient注解,将订单商品加在订单实体上,也不需要单独构造对应的传输对象。伪代码如下:

/**
 * 订单实体
 */
@Data
@Entity
@Table(name = "wms_orders")
public class WmsOrder implements Serializable {
	/**
	 * 订单号
	 */
	private String order_sn; 
	
	/**
	 * 母订单号
	 */
    private String parent_order_sn; 

	/**
	 * 下单时间
	 */
    private Date create_time; 
    
	......
	//以上都是订单表中的字段
	
	/**
	 * 客户名称
	 */
    @Transient
    private String customerTitle; 

	/**
	 * 商户名称
	 */
    @Transient
    private String companyName; 

	/**
	 * 订单商品实体
	 */
    @Transient
    private List<OrderGoods> orderGoodsList; 
}

/**
 * 订单商品实体
 */
@Data
@Entity
@Table(name = "wms_orders_goods")
public class WmsOrderGoods implements Serializable {

	/**
	 * 订单id
	 */
	private Long order_id; 
	
	/**
	 * 订单号
	 */
	private String order_sn; 
	
	/**
	 * 订单商品名称
	 */
    private String goods_name; 

	/**
	 * 订单商品数量
	 */
   private Integer goods_num;
    
	......
}

//推送订单消息方法
public void pushMsg(Order orderInfo){

  	//根据不同的tag选择推送不同的字段
     byte[] bytes = null;
     Message message = null;
     SendResult sendResult = null;
     Producer producer = MqUtils.getInstance().getPlusOrderProducer();

     //获取订单商品集合
     List<OrderGoods> orderGoodsList = orderGoodsService.findByOrderId(orderInfo.getId());
     if (orderGoodsList != null && !orderGoodsList.isEmpty()) {
         orderInfo.setOrderGoods(orderGoodsList);
     }
     
     bytes = new Gson().toJson(orderInfo).getBytes();
     message = new Message(Global.getConfig("TID_UQI_ORDER_UPLUS"), Global.getConfig("TAG_UQI_ORDER_UPLUS"), bytes);
     try {
         //sendResult = producer.send(message, Global.getConfig("TAG_UQI_ORDER_UPLUS"));
         //无序消息可以广播,有序消息不可以广播
         sendResult = producer.send(message);
         // 发送消息,只要不抛异常就是成功
         if (sendResult != null) {
             logger.debug(new Date() + " 发送消息成功! Topic 是:" + message.getTopic() + " msgId 是: " + message.getMsgID() + " msgTag 是:" + message.getTag());
             logger.info("消息内容是" + message.getBody());
         }
     } catch (Exception e) {
         // 消息发送失败,需要进行重试处理,可重新发送这条消息或持久化这条数据进行补偿处理
         logger.debug(new Date() + " Send mq message failed. Topic is:" + message.getTopic());
         e.printStackTrace();
     }
}

3. 问题

在使用这个注解的时候,遇到过一个问题,提示:Unknown Column ‘old_goods_num’ …,如下:

@Transient 注解使用

很明显,提示数据库表中没有对应的字段,说明@Transient注解使用无效,并没有起到临时属性的作用。原因是此注解只能在get()方法上生效。

项目中使用了@Data注解可以不用写属性的get/set方法,而订单商品实体上,之前没有使用该注解,是后来加上的,所以有一部分字段写了get/set方法,有一部分字段没有写。昨天加上了一个临时字段,查询列表就出现了上面的问题,才知道使用过程中我们需要注意的这个问题。

另外,在

javax.persistence.Transient 和 org.springframework.data.annotation.Transient 包下都有相同名称的注解,我们使用的注解应该是前者,不起作用也有可能是导错包的原因。

【总结】

知道了这个注解的用法,很多时候,我们就可以很方便很快地处理接口对应地一些修改。

工作中很多东西都是靠自己去点滴积累的,有些东西虽然简单,但也有一个从不知道到知道的过程。