Java 如何在JPA中保持原始时区

Java 如何在JPA中保持原始时区,java,hibernate,jpa,Java,Hibernate,Jpa,特别是在SQL server中(我假设还有其他类型),有一种datetimeoffset类型可以使用GMT以外的偏移量存储时间戳(类似于“1998-01-01 15:00:00.0000000+06:00”) 但是,试图将日历持久化到数据库会自动将其转换为GMT(尽管我不知道是谁在做JPA、Hibernate还是sqljdbc),所以它看起来像“1998-01-01 9:00:00.0000000+00:00” 有没有办法防止某些属性出现这种情况?尽管可能有其他使用SQL数据库的解决方案,但我始

特别是在SQL server中(我假设还有其他类型),有一种datetimeoffset类型可以使用GMT以外的偏移量存储时间戳(类似于“1998-01-01 15:00:00.0000000+06:00”)

但是,试图将日历持久化到数据库会自动将其转换为GMT(尽管我不知道是谁在做JPA、Hibernate还是sqljdbc),所以它看起来像“1998-01-01 9:00:00.0000000+00:00”


有没有办法防止某些属性出现这种情况?

尽管可能有其他使用SQL数据库的解决方案,但我始终遵循以下方法:

在数据库或文件中,我总是以UTC存储时间,没有例外。 如果必须保留时区(例如UI),则我会将UTC偏移量存储在一个额外字段中

这样我就有了正确的时间,如果需要的话,时区也会保留下来。

找到了一种方法

package testers.jpa.beans;

import java.io.Serializable;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;

import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.Transient;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

@Entity
@Table(name = "dbo.tester")
//Ignore the actual calendar object here because the jsonner strips the timezone info from it
@JsonIgnoreProperties(value = { "date" })
@NamedQuery(name = "NameAndOffset.purge", query = "DELETE FROM NameAndOffset")
public class NameAndOffset implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private Calendar date;

    public NameAndOffset() {

    }

    public NameAndOffset(String name, Calendar date) {
        this.name = name;
        this.date = date;
    }

    @Id
    @Basic
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    /**
     * Return a string representation of the {@link NameAndOffset#date} object.<br/>
     * This will be used by JPA and the Jsonner to prevent losing the timezone
     * information<br/>
     * <br/>
     * For this to work properly you must tell both the Json mapper and JPA to
     * ignore anything else that relates to the {@link NameAndOffset#date} field
     * 
     * @return A String representation of the {@link NameAndOffset#date} field,
     *         formatted for SQL Server's datetimeoffset data type
     */
    @Basic
    @Column(name = "date")
    public String getDateTimeOffset() {
        return new OffsetDateFormat().formatCalendar(date);
    }

    public void setDateTimeOffset(String date) throws ParseException {
        this.date = new OffsetDateFormat().parseCalendar(date);
    }

    //Ignore the actual calendar object here because JPA strips the timezone info from it
    @Transient
    public Calendar getDate() {
        return date;
    }

    public void setDate(Calendar date) throws ParseException {
        this.date = date;
    }

    class OffsetDateFormat extends SimpleDateFormat {

        private static final long serialVersionUID = 1L;
        private static final String OFFSET_FORMAT = "yyyy-MM-dd HH:mm:ss.S Z";

        public OffsetDateFormat() {
            super(OFFSET_FORMAT);
        }

        public Calendar parseCalendar(String source) throws ParseException {
            //pull out the colon in the offset
            int timeZoneColon = source.lastIndexOf(":");
            String nocolon = source.substring(0, timeZoneColon) + source.substring(timeZoneColon + 1);

            Calendar cal = Calendar.getInstance();

            cal.setTime(parse(nocolon));
            //after parsing, the timezone of this DateFormatter changes to whatever was represented in the string
            //make sure the new calendar reflects this
            cal.setTimeZone(getTimeZone());
            return cal;
        }

        public String formatCalendar(Calendar calendar) {
            setTimeZone(calendar.getTimeZone());
            String nocolon = format(calendar.getTime());

            //add the colon
            StringBuffer sb = new StringBuffer(nocolon.substring(0, nocolon.length() - 2)).append(":").append(nocolon.substring(nocolon.length() - 2));

            return sb.toString();
        }

    }

}
packagetesters.jpa.beans;
导入java.io.Serializable;
导入java.text.ParseException;
导入java.text.simpleDataFormat;
导入java.util.Calendar;
导入javax.persistence.Basic;
导入javax.persistence.Column;
导入javax.persistence.Entity;
导入javax.persistence.Id;
导入javax.persistence.NamedQuery;
导入javax.persistence.Table;
导入javax.persistence.Transient;
导入com.fasterxml.jackson.annotation.JsonIgnoreProperties;
@实体
@表(name=“dbo.tester”)
//忽略此处的实际日历对象,因为jsonner会从中删除时区信息
@JsonIgnoreProperties(值={“日期”})
@NamedQuery(name=“NameAndOffset.purge”,query=“从NameAndOffset删除”)
公共类名称和偏移量实现可序列化{
私有静态最终长serialVersionUID=1L;
私有字符串名称;
私人日历日期;
公共名称和偏移量(){
}
公共名称和偏移量(字符串名称、日历日期){
this.name=名称;
this.date=日期;
}
@身份证
@基本的
公共字符串getName(){
返回名称;
}
公共void集合名(字符串名){
this.name=名称;
}
/**
*返回{@link name和offset#date}对象的字符串表示形式。
*JPA和Jsonner将使用它来防止丢失时区 *信息
*
*为了让它正常工作,您必须告诉Json映射器和JPA *忽略与{@link name和offset#date}字段相关的任何其他内容 * *@返回{@link name和offset#date}字段的字符串表示形式, *为SQL Server的datetimeoffset数据类型格式化 */ @基本的 @列(name=“date”) 公共字符串getDateTimeOffset(){ 返回新的OffsetDateFormat().formatCalendar(日期); } public void setDateTimeOffset(字符串日期)引发异常{ this.date=新的OffsetDateFormat().parseCalendar(日期); } //忽略此处的实际日历对象,因为JPA会从中删除时区信息 @短暂的 公共日历getDate(){ 返回日期; } 公共无效设置日期(日历日期)引发异常{ this.date=日期; } 类OffsetDateFormat扩展了SimpleDateFormat{ 私有静态最终长serialVersionUID=1L; 私有静态最终字符串偏移量_FORMAT=“yyyy-MM-dd HH:MM:ss.S Z”; 公共OffsetDateFormat(){ super(OFFSET_格式); } 公共日历parseCalendar(字符串源)引发ParseException{ //拉出偏移量中的冒号 int timezoneclon=source.lastIndexOf(“:”); 字符串nocolon=source.substring(0,timezoneclon)+source.substring(timezoneclon+1); Calendar cal=Calendar.getInstance(); cal.setTime(解析(nocolon)); //解析后,此DateFormatter的时区将更改为字符串中表示的任何时区 //确保新日历反映了这一点 cal.setTimeZone(getTimeZone()); 返回cal; } 公共字符串格式日历(日历日历){ setTimeZone(calendar.getTimeZone()); 字符串nocolon=format(calendar.getTime()); //添加冒号 StringBuffer sb=新的StringBuffer(nocolon.substring(0,nocolon.length()-2)).append(“:”).append(nocolon.substring(nocolon.length()-2)); 使某人返回字符串(); } } }
是否有任何原因,为什么要使用UTC(=GMT)以外的其他偏移量?是的,我想记住给我的偏移量。所以当我把它发回来的时候,它的偏移量是一样的。(我希望客户在他们的时区中看到它,而不是GMT,不是我的。)但我仍然希望保存偏移量,因为我必须根据此表中恰好位于不同时区的其他值进行计算。我理解正确了吗,您不希望使用UTC作为参考吗?因此不需要GMT+6.0?你想要另一个引用,比如EST+03:00?不,结尾的“+6:00”是需要的,但每当我坚持到数据库时,它就会改变时间并使其成为“+0:00”Alex,我不能仅仅告诉一家公司“按原则”删除一个长期SQL表。因此,我询问如何通过JPA接口正确使用SQL中提供的datetimeoffset。如果我问过桌子的正确设计,那么你的回答完全符合上下文。但就目前情况而言,你仅仅说不应该这样做,并不能解决我目前的问题。事实证明,我能够想出一个相当简单的解决方案。@mike我为所有能够定义/更改其数据结构的其他人提供了一个解决方案。这个解决方案的问题是,如果您必须使用between或任何其他查询来过滤日期,因为它在您的数据库中存储为字符串。