ลำดับ JPA ไฮเบอร์เนต (ไม่ใช่รหัส)


138

มันเป็นไปได้ที่จะใช้ลำดับ DB สำหรับคอลัมน์บางอย่างที่ไม่ได้ระบุ / ไม่ได้เป็นส่วนหนึ่งของตัวระบุคอมโพสิต ?

ฉันใช้ไฮเบอร์เนตเป็นผู้ให้บริการ jpa และฉันมีตารางที่มีคอลัมน์บางคอลัมน์ที่สร้างค่า (โดยใช้ลำดับ) แม้ว่าพวกเขาจะไม่ได้เป็นส่วนหนึ่งของตัวระบุ

สิ่งที่ฉันต้องการคือการใช้ลำดับเพื่อสร้างค่าใหม่สำหรับเอนทิตีที่คอลัมน์สำหรับลำดับไม่ได้เป็น(ส่วนหนึ่ง) คีย์หลัก:

@Entity
@Table(name = "MyTable")
public class MyEntity {

    //...
    @Id //... etc
    public Long getId() {
        return id;
    }

   //note NO @Id here! but this doesn't work...
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "myGen")
    @SequenceGenerator(name = "myGen", sequenceName = "MY_SEQUENCE")
    @Column(name = "SEQ_VAL", unique = false, nullable = false, insertable = true, updatable = true)
    public Long getMySequencedValue(){
      return myVal;
    }

}

จากนั้นเมื่อฉันทำสิ่งนี้:

em.persist(new MyEntity());

รหัสจะถูกสร้างขึ้น แต่mySequenceValทรัพย์สินจะถูกสร้างขึ้นโดยผู้ให้บริการ JPA ของฉันด้วย

เพียงเพื่อทำให้สิ่งต่าง ๆ ชัดเจน: ฉันต้องการไฮเบอร์เนตเพื่อสร้างมูลค่าให้กับmySequencedValueทรัพย์สิน ฉันรู้ว่าไฮเบอร์เนตสามารถจัดการค่าที่สร้างฐานข้อมูลได้ แต่ฉันไม่ต้องการใช้ทริกเกอร์หรือสิ่งอื่นใดนอกจากไฮเบอร์เนตเองเพื่อสร้างค่าสำหรับคุณสมบัติของฉัน หาก Hibernate สามารถสร้างค่าสำหรับคีย์หลักทำไมมันไม่สามารถสร้างสำหรับคุณสมบัติแบบง่าย

คำตอบ:


77

เมื่อมองหาคำตอบของปัญหานี้ฉันก็สะดุด ลิงค์นี้

ดูเหมือนว่า Hibernate / JPA ไม่สามารถสร้างค่าสำหรับคุณสมบัติที่ไม่ใช่รหัสของคุณโดยอัตโนมัติ @GeneratedValueคำอธิบายประกอบจะใช้ร่วมกับ@Idการสร้างตัวเลขอัตโนมัติ

@GeneratedValueคำอธิบายประกอบเพียงแค่บอก Hibernate ว่าฐานข้อมูลที่มีการสร้างค่านี้เอง

โซลูชัน (หรือวิธีแก้ไข) ที่แนะนำในฟอรัมนั้นคือการสร้างเอนทิตีแยกต่างหากด้วยรหัสที่สร้างขึ้นดังนี้:

@Entity
GeneralSequenceNumber คลาสสาธารณะ {
  @ id
  @GeneratedValue ( ... )
  หมายเลขยาวส่วนตัว
}

@Entity 
MyEntity ระดับสาธารณะ
  @Id ..
  รหัสส่วนตัวยาว

  @หนึ่งต่อหนึ่ง(...)
  ส่วนตัว GeneralSequnce จำนวน myVal;
}

จาก java doc ของ @GeneratedValue: "คำอธิบายประกอบ GeneratedValue อาจนำไปใช้กับคุณสมบัติหรือคีย์หลักของเอนทิตีหรือแมปซู
เปอร์คลาสที่

11
ฉันพบว่า @Column (columnDefinition = "serial") ทำงานได้อย่างสมบูรณ์แบบ แต่สำหรับ PostgreSQL เท่านั้น สำหรับฉันนี่เป็นโซลูชันที่สมบูรณ์แบบเพราะเอนทิตีที่สองคือตัวเลือก "น่าเกลียด"
Sergey Vedernikov

@SergeyVedernikov ที่เป็นประโยชน์อย่างมาก คุณคิดว่าการโพสต์นั้นเป็นคำตอบที่แยกจากกันหรือไม่? มันแก้ไขปัญหาของฉันได้อย่างง่ายดายและมีประสิทธิภาพมาก
Matt Ball

@MattBall ฉันได้โพสต์สิ่งนี้เป็นคำตอบที่แยกจากกัน :) stackoverflow.com/a/10647933/620858
Sergey Vedernikov

1
ฉันได้เปิดข้อเสนอเพื่ออนุญาต@GeneratedValueในฟิลด์ที่ไม่ใช่รหัส โปรดลงคะแนนให้รวมอยู่ใน 2.2 java.net/jira/browse/JPA_SPEC-113
Petar Tahchiev

44

ฉันพบว่าใช้@Column(columnDefinition="serial")งานได้ดี แต่สำหรับ PostgreSQL เท่านั้น สำหรับฉันนี่เป็นโซลูชั่นที่สมบูรณ์แบบเพราะเอนทิตีที่สองคือตัวเลือก "น่าเกลียด"


สวัสดีฉันต้องการคำอธิบาย คุณช่วยบอกฉันเพิ่มเติมได้ไหม
Emaborsa

2
@Emaborsa columnDefinition=โดยพื้นฐานแล้วบิตบอกให้ Hiberate ไม่พยายามสร้างนิยามคอลัมน์และใช้ข้อความที่คุณให้มาแทน โดยพื้นฐานแล้ว DDL ของคุณสำหรับคอลัมน์จะเป็นชื่อ + columnDefinition อย่างแท้จริง ในกรณีนี้ (PostgreSQL) mycolumn serialเป็นคอลัมน์ที่ถูกต้องในตาราง
Patrick

7
เทียบเท่ากับ MySQL คือ@Column(columnDefinition = "integer auto_increment")
Richard Kennard

2
ออโต้นี้สร้างมูลค่าได้หรือไม่? ฉันพยายามสร้างเอนทิตีด้วยคำจำกัดความของฟิลด์เช่นนี้ แต่ไม่ได้สร้างค่า มันโยนค่าว่างในคอลัมน์ <column> ละเมิดข้อ จำกัด ที่ไม่ใช่ null
KyelJmD

7
ฉันใช้@Column(insertable = false, updatable = false, columnDefinition="serial")เพื่อป้องกันการจำศีลจากการพยายามแทรกค่า Null หรืออัพเดทฟิลด์ จากนั้นคุณต้องสอบถาม db อีกครั้งเพื่อรับ id ที่สร้างขึ้นหลังจากการแทรกหากคุณจำเป็นต้องใช้มันทันที
Robert Di Paolo

20

ฉันรู้ว่านี่เป็นคำถามที่เก่ามาก แต่มันแสดงให้เห็นในตอนแรกเกี่ยวกับผลลัพธ์และ jpa มีการเปลี่ยนแปลงมากมายตั้งแต่คำถาม

วิธีที่ถูกต้องที่จะทำตอนนี้คือการใส่@Generatedคำอธิบายประกอบ คุณสามารถกำหนดลำดับตั้งค่าเริ่มต้นในคอลัมน์เป็นลำดับนั้นแล้วแมปคอลัมน์เป็น:

@Generated(GenerationTime.INSERT)
@Column(name = "column_name", insertable = false)

1
สิ่งนี้ยังต้องการค่าที่จะถูกสร้างขึ้นโดยฐานข้อมูลซึ่งไม่ได้ตอบคำถามจริงๆ สำหรับฐานข้อมูล Oracle ก่อนหน้า 12c คุณยังคงต้องเขียนทริกเกอร์ฐานข้อมูลเพื่อสร้างค่า
Bernie

9
นี่เป็นบันทึกย่อแบบไฮเบอร์เนตไม่ใช่ JPA
caarlos0

14

ไฮเบอร์เนตรองรับสิ่งนี้อย่างแน่นอน จากเอกสาร:

"คุณสมบัติที่สร้างขึ้นเป็นคุณสมบัติที่มีค่าที่สร้างขึ้นโดยฐานข้อมูลโดยทั่วไปแอปพลิเคชันไฮเบอร์เนตที่จำเป็นในการรีเฟรชวัตถุที่มีคุณสมบัติใด ๆ ที่ฐานข้อมูลกำลังสร้างค่าอย่างไรก็ตามการทำเครื่องหมายคุณสมบัติตามที่สร้างขึ้น โดยพื้นฐานแล้วเมื่อใดก็ตามที่ Hibernate ออก SQL INSERT หรือ UPDATE สำหรับเอนทิตีที่กำหนดคุณสมบัติที่สร้างขึ้นมันจะออกตัวเลือกทันทีหลังจากนั้นเพื่อดึงค่าที่สร้างขึ้นมา "

สำหรับคุณสมบัติที่สร้างจากการแทรกเท่านั้นการแมปคุณสมบัติของคุณ (.hbm.xml) จะมีลักษณะดังนี้:

<property name="foo" generated="insert"/>

สำหรับคุณสมบัติที่สร้างขึ้นจากการแทรกและอัพเดตการแมปคุณสมบัติของคุณ (.hbm.xml) จะมีลักษณะดังนี้:

<property name="foo" generated="always"/>

น่าเสียดายที่ฉันไม่รู้จัก JPA ดังนั้นฉันจึงไม่รู้ว่าคุณลักษณะนี้มีการเปิดเผยผ่าน JPA หรือไม่ (ฉันสงสัยว่าไม่ได้)

หรือมิฉะนั้นคุณควรจะสามารถแยกคุณสมบัติออกจากส่วนแทรกและอัพเดตจากนั้นเรียกเซสชันด้วยตนเอง "รีเฟรช" (obj); หลังจากที่คุณแทรก / อัพเดทเพื่อโหลดค่าที่สร้างจากฐานข้อมูล

นี่คือวิธีที่คุณจะแยกคุณสมบัติจากการใช้ในการแทรกและอัพเดตคำสั่ง:

<property name="foo" update="false" insert="false"/>

อีกครั้งฉันไม่รู้ว่า JPA เปิดเผยคุณสมบัติไฮเบอร์เนตเหล่านี้หรือไม่ แต่ไฮเบอร์เนตรองรับคุณสมบัติเหล่านั้นหรือไม่


1
คำอธิบายประกอบ @Generated สอดคล้องกับการกำหนดค่า XML ข้างต้น ดูส่วนนี้ของเอกสารจำศีลสำหรับรายละเอียดเพิ่มเติม
Eric

8

ในฐานะผู้ติดตามต่อไปนี้เป็นวิธีที่ฉันทำให้มันทำงาน:

@Override public Long getNextExternalId() {
    BigDecimal seq =
        (BigDecimal)((List)em.createNativeQuery("select col_msd_external_id_seq.nextval from dual").getResultList()).get(0);
    return seq.longValue();
}

varient กับ Hibernate 4.2.19 และ Oracle: SQLQuery sqlQuery = getSession().createSQLQuery("select NAMED_SEQ.nextval seq from dual"); sqlQuery.addScalar("seq", LongType.INSTANCE); return (Long) sqlQuery.uniqueResult();
แอรอน

6

ฉันแก้ไขการสร้าง UUID (หรือลำดับ) ด้วย Hibernate โดยใช้@PrePersistคำอธิบายประกอบ:

@PrePersist
public void initializeUUID() {
    if (uuid == null) {
        uuid = UUID.randomUUID().toString();
    }
}

5

แม้ว่านี่จะเป็นเธรดเก่าที่ฉันต้องการแชร์โซลูชันของฉันและหวังว่าจะได้รับคำติชมเกี่ยวกับสิ่งนี้ ถูกเตือนว่าฉันทดสอบโซลูชันนี้กับฐานข้อมูลโลคัลของฉันในบาง testcase JUnit เท่านั้น ดังนั้นนี่ไม่ใช่คุณลักษณะที่มีประสิทธิภาพจนถึงตอนนี้

ฉันแก้ไขปัญหานั้นด้วยการแนะนำบันทึกย่อที่กำหนดเองที่เรียกว่าลำดับที่ไม่มีคุณสมบัติ มันเป็นเพียงเครื่องหมายสำหรับเขตข้อมูลที่ควรกำหนดค่าจากลำดับที่เพิ่มขึ้น

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Sequence
{
}

การใช้คำอธิบายประกอบนี้ฉันทำเครื่องหมายเอนทิตีของฉัน

public class Area extends BaseEntity implements ClientAware, IssuerAware
{
    @Column(name = "areaNumber", updatable = false)
    @Sequence
    private Integer areaNumber;
....
}

เพื่อรักษาฐานข้อมูลสิ่งต่าง ๆ ฉันแนะนำเอนทิตีที่เรียกว่า SequenceNumber ซึ่งเก็บค่าปัจจุบันตามลำดับและขนาดเพิ่มขึ้น ฉันเลือก className เป็นคีย์เฉพาะเพื่อให้แต่ละคลาสเอนทิตีจะได้รับลำดับของตนเอง

@Entity
@Table(name = "SequenceNumber", uniqueConstraints = { @UniqueConstraint(columnNames = { "className" }) })
public class SequenceNumber
{
    @Id
    @Column(name = "className", updatable = false)
    private String className;

    @Column(name = "nextValue")
    private Integer nextValue = 1;

    @Column(name = "incrementValue")
    private Integer incrementValue = 10;

    ... some getters and setters ....
}

ขั้นตอนสุดท้ายและที่ยากที่สุดคือ PreInsertListener ที่จัดการการกำหนดหมายเลขลำดับ โปรดทราบว่าฉันใช้สปริงเป็นที่เก็บถั่ว

@Component
public class SequenceListener implements PreInsertEventListener
{
    private static final long serialVersionUID = 7946581162328559098L;
    private final static Logger log = Logger.getLogger(SequenceListener.class);

    @Autowired
    private SessionFactoryImplementor sessionFactoryImpl;

    private final Map<String, CacheEntry> cache = new HashMap<>();

    @PostConstruct
    public void selfRegister()
    {
        // As you might expect, an EventListenerRegistry is the place with which event listeners are registered
        // It is a service so we look it up using the service registry
        final EventListenerRegistry eventListenerRegistry = sessionFactoryImpl.getServiceRegistry().getService(EventListenerRegistry.class);

        // add the listener to the end of the listener chain
        eventListenerRegistry.appendListeners(EventType.PRE_INSERT, this);
    }

    @Override
    public boolean onPreInsert(PreInsertEvent p_event)
    {
        updateSequenceValue(p_event.getEntity(), p_event.getState(), p_event.getPersister().getPropertyNames());

        return false;
    }

    private void updateSequenceValue(Object p_entity, Object[] p_state, String[] p_propertyNames)
    {
        try
        {
            List<Field> fields = ReflectUtil.getFields(p_entity.getClass(), null, Sequence.class);

            if (!fields.isEmpty())
            {
                if (log.isDebugEnabled())
                {
                    log.debug("Intercepted custom sequence entity.");
                }

                for (Field field : fields)
                {
                    Integer value = getSequenceNumber(p_entity.getClass().getName());

                    field.setAccessible(true);
                    field.set(p_entity, value);
                    setPropertyState(p_state, p_propertyNames, field.getName(), value);

                    if (log.isDebugEnabled())
                    {
                        LogMF.debug(log, "Set {0} property to {1}.", new Object[] { field, value });
                    }
                }
            }
        }
        catch (Exception e)
        {
            log.error("Failed to set sequence property.", e);
        }
    }

    private Integer getSequenceNumber(String p_className)
    {
        synchronized (cache)
        {
            CacheEntry current = cache.get(p_className);

            // not in cache yet => load from database
            if ((current == null) || current.isEmpty())
            {
                boolean insert = false;
                StatelessSession session = sessionFactoryImpl.openStatelessSession();
                session.beginTransaction();

                SequenceNumber sequenceNumber = (SequenceNumber) session.get(SequenceNumber.class, p_className);

                // not in database yet => create new sequence
                if (sequenceNumber == null)
                {
                    sequenceNumber = new SequenceNumber();
                    sequenceNumber.setClassName(p_className);
                    insert = true;
                }

                current = new CacheEntry(sequenceNumber.getNextValue() + sequenceNumber.getIncrementValue(), sequenceNumber.getNextValue());
                cache.put(p_className, current);
                sequenceNumber.setNextValue(sequenceNumber.getNextValue() + sequenceNumber.getIncrementValue());

                if (insert)
                {
                    session.insert(sequenceNumber);
                }
                else
                {
                    session.update(sequenceNumber);
                }
                session.getTransaction().commit();
                session.close();
            }

            return current.next();
        }
    }

    private void setPropertyState(Object[] propertyStates, String[] propertyNames, String propertyName, Object propertyState)
    {
        for (int i = 0; i < propertyNames.length; i++)
        {
            if (propertyName.equals(propertyNames[i]))
            {
                propertyStates[i] = propertyState;
                return;
            }
        }
    }

    private static class CacheEntry
    {
        private int current;
        private final int limit;

        public CacheEntry(final int p_limit, final int p_current)
        {
            current = p_current;
            limit = p_limit;
        }

        public Integer next()
        {
            return current++;
        }

        public boolean isEmpty()
        {
            return current >= limit;
        }
    }
}

ดังที่คุณเห็นจากโค้ดด้านบนผู้ฟังใช้อินสแตนซ์ SequenceNumber หนึ่งรายการต่อคลาสเอนทิตี้และสำรองหมายเลขลำดับสองชุดที่กำหนดโดย incrementValue ของเอนทิตี SequenceNumber หากจำนวนซีเควนซ์นั้นหมดมันจะโหลดเอนทิตี SequenceNumber สำหรับคลาสเป้าหมายและสำรองค่า incrementValue สำหรับการโทรครั้งต่อไป วิธีนี้ฉันไม่จำเป็นต้องค้นหาฐานข้อมูลทุกครั้งที่ต้องการค่าลำดับ หมายเหตุ StatelessSession ที่กำลังเปิดเพื่อจองชุดหมายเลขลำดับถัดไป คุณไม่สามารถใช้เซสชันเดียวกันกับที่เอนทิตีเป้าหมายยังคงอยู่เนื่องจากจะทำให้ ConcurrentModificationException ใน EntityPersister

หวังว่านี่จะช่วยใครซักคน


5

หากคุณใช้ postgresql
และฉันกำลังใช้งานใน boot boot 1.5.6

@Column(columnDefinition = "serial")
@Generated(GenerationTime.INSERT)
private Integer orderID;

1
มันใช้งานได้สำหรับฉันเช่นกันฉันใช้ spring boot 2.1.6.RELEASE, Hibernate 5.3.10 สุดท้ายแล้วสิ่งที่ได้ชี้ไปแล้วฉันต้องสร้าง secuence seq_orderและสร้างแบบฟอร์มอ้างอิงฟิลด์ nextval('seq_order'::regclass)
OJVM

3

ฉันทำงานในสถานการณ์เดียวกันเช่นคุณและฉันยังไม่พบคำตอบที่จริงจังหากเป็นไปได้ที่จะสร้างคุณสมบัติที่ไม่ใช่รหัสกับ JPA หรือไม่

วิธีแก้ปัญหาของฉันคือการเรียกลำดับด้วยเคียวรี JPA ดั้งเดิมเพื่อตั้งค่าคุณสมบัติด้วยมือก่อนจะยืนยัน

สิ่งนี้ไม่เป็นที่น่าพอใจ แต่มันก็เป็นวิธีแก้ปัญหาสำหรับช่วงเวลา

มาริโอ


2

ฉันพบบันทึกย่อนี้ในเซสชัน 9.1.9 GeneratedValue Annotation จากข้อมูลจำเพาะ JPA: "[43] แอปพลิเคชันแบบพกพาไม่ควรใช้คำอธิบายประกอบ GeneratedValue ในฟิลด์หรือคุณสมบัติถาวรอื่น ๆ " ดังนั้นฉันคิดว่ามันเป็นไปไม่ได้ที่จะสร้างมูลค่าอัตโนมัติสำหรับค่าคีย์หลักที่ไม่ใช่อย่างน้อยก็ใช้ JPA เพียงอย่างเดียว


1

ดูเหมือนว่าเธรดจะแก่แล้วฉันแค่ต้องการเพิ่มโซลูชันของฉันที่นี่ (ใช้ AspectJ - AOP ในฤดูใบไม้ผลิ)

วิธีแก้ไขคือการสร้างคำอธิบายประกอบที่กำหนดเอง@InjectSequenceValueดังนี้

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface InjectSequenceValue {
    String sequencename();
}

ตอนนี้คุณสามารถใส่คำอธิบายประกอบในฟิลด์ใด ๆ ในเอนทิตีเพื่อให้ค่าฟิลด์ (ยาว / จำนวนเต็ม) จะถูกฉีดที่รันไทม์โดยใช้ค่าถัดไปของลำดับ

คำอธิบายประกอบเช่นนี้

//serialNumber will be injected dynamically, with the next value of the serialnum_sequence.
 @InjectSequenceValue(sequencename = "serialnum_sequence") 
  Long serialNumber;

จนถึงตอนนี้เราได้ทำเครื่องหมายฟิลด์ที่เราต้องการฉีดค่าลำดับดังนั้นเราจะดูวิธีการฉีดค่าลำดับไปยังฟิลด์ที่ทำเครื่องหมายซึ่งจะทำโดยการสร้างจุดตัดใน AspectJ

เราจะทริกเกอร์การฉีดก่อนที่save/persistวิธีการจะถูกดำเนินการซึ่งจะทำในชั้นด้านล่าง

@Aspect
@Configuration
public class AspectDefinition {

    @Autowired
    JdbcTemplate jdbcTemplate;


    //@Before("execution(* org.hibernate.session.save(..))") Use this for Hibernate.(also include session.save())
    @Before("execution(* org.springframework.data.repository.CrudRepository.save(..))") //This is for JPA.
    public void generateSequence(JoinPoint joinPoint){

        Object [] aragumentList=joinPoint.getArgs(); //Getting all arguments of the save
        for (Object arg :aragumentList ) {
            if (arg.getClass().isAnnotationPresent(Entity.class)){ // getting the Entity class

                Field[] fields = arg.getClass().getDeclaredFields();
                for (Field field : fields) {
                    if (field.isAnnotationPresent(InjectSequenceValue.class)) { //getting annotated fields

                        field.setAccessible(true); 
                        try {
                            if (field.get(arg) == null){ // Setting the next value
                                String sequenceName=field.getAnnotation(InjectSequenceValue.class).sequencename();
                                long nextval=getNextValue(sequenceName);
                                System.out.println("Next value :"+nextval); //TODO remove sout.
                                field.set(arg, nextval);
                            }

                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }

        }
    }

    /**
     * This method fetches the next value from sequence
     * @param sequence
     * @return
     */

    public long getNextValue(String sequence){
        long sequenceNextVal=0L;

        SqlRowSet sqlRowSet= jdbcTemplate.queryForRowSet("SELECT "+sequence+".NEXTVAL as value FROM DUAL");
        while (sqlRowSet.next()){
            sequenceNextVal=sqlRowSet.getLong("value");

        }
        return  sequenceNextVal;
    }
}

ตอนนี้คุณสามารถใส่คำอธิบายประกอบนิติบุคคลใด ๆ ดังต่อไปนี้

@Entity
@Table(name = "T_USER")
public class UserEntity {

    @Id
    @SequenceGenerator(sequenceName = "userid_sequence",name = "this_seq")
    @GeneratedValue(strategy = GenerationType.SEQUENCE,generator = "this_seq")
    Long id;
    String userName;
    String password;

    @InjectSequenceValue(sequencename = "serialnum_sequence") // this will be injected at the time of saving.
    Long serialNumber;

    String name;
}

0

"ฉันไม่ต้องการใช้ทริกเกอร์หรือสิ่งอื่นใดนอกจากไฮเบอร์เนตเพื่อสร้างมูลค่าให้กับอสังหาริมทรัพย์ของฉัน"

ในกรณีนั้นวิธีการเกี่ยวกับการสร้างการใช้งานของ UserType ซึ่งสร้างค่าที่ต้องการและการกำหนดค่าเมตาดาต้าที่จะใช้ UserType นั้นสำหรับการคงอยู่ของคุณสมบัติ mySequenceVal?


0

สิ่งนี้ไม่เหมือนกับการใช้ลำดับ เมื่อใช้ลำดับคุณไม่ได้แทรกหรืออัปเดตอะไรเลย คุณเพียงแค่ดึงค่าลำดับถัดไป ดูเหมือนว่าไฮเบอร์เนตไม่รองรับ


0

หากคุณมีคอลัมน์ที่มีประเภท UNIQUEIDENTIFIER และการสร้างเริ่มต้นที่จำเป็นในการแทรก แต่คอลัมน์ไม่ใช่ PK

@Generated(GenerationTime.INSERT)
@Column(nullable = false , columnDefinition="UNIQUEIDENTIFIER")
private String uuidValue;

ใน db คุณจะมี

CREATE TABLE operation.Table1
(
    Id         INT IDENTITY (1,1)               NOT NULL,
    UuidValue  UNIQUEIDENTIFIER DEFAULT NEWID() NOT NULL)

ในกรณีนี้คุณจะไม่กำหนดตัวกำเนิดสำหรับค่าที่คุณต้องการ (มันจะขอบคุณโดยอัตโนมัติ columnDefinition="UNIQUEIDENTIFIER") เช่นเดียวกับที่คุณสามารถลองใช้กับคอลัมน์ประเภทอื่น


0

ฉันพบวิธีแก้ปัญหาสำหรับสิ่งนี้ในฐานข้อมูล MySql โดยใช้ @PostConstruct และ JdbcTemplate ในแอปพลิเคชัน Spring อาจทำได้กับฐานข้อมูลอื่น แต่กรณีการใช้งานที่ฉันจะนำเสนอขึ้นอยู่กับประสบการณ์ของฉันกับ MySql เนื่องจากใช้ auto_increment

ก่อนอื่นฉันได้ลองกำหนดคอลัมน์เป็น auto_increment โดยใช้คุณสมบัติ ColumnDefinition ของคำอธิบายประกอบ @Column แต่มันไม่ทำงานเนื่องจากคอลัมน์ที่จำเป็นต้องใช้เป็นกุญแจในการเพิ่มขึ้นอัตโนมัติ แต่ดูเหมือนว่าคอลัมน์จะไม่ถูกกำหนดเป็น ดัชนีจนกระทั่งหลังจากถูกกำหนดทำให้เกิดการหยุดชะงัก

ที่นี่ฉันมาพร้อมกับความคิดในการสร้างคอลัมน์โดยไม่มีข้อกำหนด auto_increment และเพิ่มหลังจากนั้นสร้างฐานข้อมูล สิ่งนี้เป็นไปได้โดยใช้คำอธิบายประกอบ @PostConstruct ซึ่งทำให้วิธีการถูกเรียกใช้ทันทีหลังจากที่แอปพลิเคชันเริ่มต้นการทำงานของ beans รวมถึงวิธีการอัปเดตของ JdbcTemplate

รหัสดังต่อไปนี้:

ในเอนทิตีของฉัน:

@Entity
@Table(name = "MyTable", indexes = { @Index(name = "my_index", columnList = "mySequencedValue") })
public class MyEntity {
    //...
    @Column(columnDefinition = "integer unsigned", nullable = false, updatable = false, insertable = false)
    private Long mySequencedValue;
    //...
}

ในคลาส PostConstructComponent:

@Component
public class PostConstructComponent {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @PostConstruct
    public void makeMyEntityMySequencedValueAutoIncremental() {
        jdbcTemplate.update("alter table MyTable modify mySequencedValue int unsigned auto_increment");
    }
}

0

ฉันต้องการให้ทางเลือกถัดจากโซลูชั่นที่ได้รับการยอมรับของ @Morten Berg ซึ่งทำงานได้ดีกว่าสำหรับฉัน

วิธีการนี้จะช่วยให้การกำหนดเขตที่มีที่ต้องการจริงNumberประเภท - Longในกรณีที่ใช้งานของฉัน - GeneralSequenceNumberแทน สิ่งนี้มีประโยชน์เช่นสำหรับ JSON (de-) การทำให้เป็นอนุกรม

ข้อเสียคือต้องการฐานข้อมูลเพิ่มเติมเล็กน้อย


ก่อนอื่นเราต้องการสิ่งActualEntityที่เราต้องการเพิ่มgeneratedประเภทโดยอัตโนมัติLong:

// ...
@Entity
public class ActualEntity {

    @Id 
    // ...
    Long id;

    @Column(unique = true, updatable = false, nullable = false)
    Long generated;

    // ...

}

Generatedต่อไปเราต้องช่วยที่นิติบุคคล ฉันวางแพคเกจส่วนตัวข้างActualEntityเพื่อให้รายละเอียดการใช้งานของแพคเกจ:

@Entity
class Generated {

    @Id
    @GeneratedValue(strategy = SEQUENCE, generator = "seq")
    @SequenceGenerator(name = "seq", initialValue = 1, allocationSize = 1)
    Long id;

}

ActualEntityสุดท้ายเราต้องการสถานที่ที่จะเบ็ดในด้านขวาก่อนที่เราจะประหยัด ที่นั่นเราสร้างและยืนยันGeneratedอินสแตนซ์ นี้จากนั้นจะให้ลำดับฐานข้อมูลที่สร้างขึ้นจากประเภทid Longเราใช้ประโยชน์จากคุณค่านี้โดยการเขียนถึงActualEntity.generatedเราทำให้การใช้งานของค่านี้โดยการเขียนไปยัง

ในกรณีการใช้งานของฉันฉันใช้สิ่งนี้โดยใช้ Spring Data REST @RepositoryEventHandlerซึ่งถูกเรียกใช้ก่อนที่จะActualEntityได้รับการยืนยัน มันควรแสดงให้เห็นถึงหลักการ:

@Component
@RepositoryEventHandler
public class ActualEntityHandler {

    @Autowired
    EntityManager entityManager;

    @Transactional
    @HandleBeforeCreate
    public void generate(ActualEntity entity) {
        Generated generated = new Generated();

        entityManager.persist(generated);
        entity.setGlobalId(generated.getId());
        entityManager.remove(generated);
    }

}

ฉันไม่ได้ทดสอบมันในแอปพลิเคชันในชีวิตจริงดังนั้นโปรดเพลิดเพลินด้วยความระมัดระวัง


-1

ฉันอยู่ในสถานการณ์เช่นคุณ (ลำดับ JPA / ไฮเบอร์เนตสำหรับฟิลด์ที่ไม่ใช่ @Id) และฉันสิ้นสุดการสร้างทริกเกอร์ใน db schema ของฉันที่เพิ่มหมายเลขลำดับที่ไม่ซ้ำกันในส่วนแทรก ฉันไม่เคยได้รับมันเพื่อทำงานกับ JPA / Hibernate


-1

หลังจากใช้เวลาหลายชั่วโมงสิ่งนี้ช่วยให้ฉันแก้ปัญหาได้อย่างเป็นระเบียบ:

สำหรับ Oracle 12c:

ID NUMBER GENERATED as IDENTITY

สำหรับ H2:

ID BIGINT GENERATED as auto_increment

ทำยัง:

@Column(insertable = false)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.