How can I get JPA hibernate to not adding a value NULL, when field is not set (null), when inserting into a db/2 default value field

huangapple go评论70阅读模式
英文:

How can I get JPA hibernate to not adding a value NULL, when field is not set (null), when inserting into a db/2 default value field

问题

以下是您要翻译的内容:

我正在使用Java和Hibernate JPA5.6.15.Final编写一个应用程序来存储/更改/删除合同和处理数据

- IBM DB/2数据库中的两个数据库表简化):
CREATE TABLE "Schema1"."Contracts" (
    "IDX" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY,
    "DATA" varchar(255));

CREATE TABLE "Schema1"."Processes" (
    "IDX" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY,
    "CONTRACTIDX" INTEGER NOT NULL DEFAULT 0,
    "DATA" varchar(255));

数据库已经存在,而且非常老,其他软件也在使用它(对于其中一个应用程序,我们甚至没有源代码),因此我们不能轻松地对表和字段定义进行更改。

CONTRACTIDX可能默认为0的原因是,在没有合同的情况下也应该能够进行处理(这意味着:当我们在长时间的合同谈判中时,我们也可以进行处理,而当我们已经有合同时也可以进行处理)。因此,CONTRACTIDX的有效值应为现有合同ID或0以表示没有合同。

实体类:

src/main/java/mypackage/ProcessDTO.java

@Entity
@Table(schema = "Schema1", name = "Processes")
public class ProcessDTO
{
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "IDX")
  private Long id = null;

  @ManyToOne
  @JoinColumn(name = "CONTRACTIDX" /* , nullable=true or nullable=false: I've already tried that... it has no effect on the problem */)
  private ContractDTO contract = null;

  @Column(name="DATA")
  private String data;

  // getters and setters
}

src/main/java/mypackage/ContractDTO.java

@Entity
@Table(schema = "SCHEMA1", name = "Contracts")
public class ContractDTO {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "IDX")
  private Long id;

  @Column(name="DATA")
  private String data;

  // getters and setters
}

src/main/resources/META-INF/persistence.xml:

<?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?>
<persistence xmlns=&quot;http://java.sun.com/xml/ns/persistence&quot;
  xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
  xsi:schemaLocation=&quot;http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd&quot;
  version=&quot;2.0&quot;>
  <persistence-unit name=&quot;myPu&quot;>
    <jta-data-source>jdbc/myDb</jta-data-source>
    
    <class>mypackage.ProcessDTO</class>
    <class>mypackage.ContractDTO</class>
   
    <properties>
      <property name=&quot;hibernate.dialect&quot; value= &quot;org.hibernate.dialect.DB2Dialect&quot; />
      <property name=&quot;hibernate.show_sql&quot; value=&quot;true&quot; />
      <property name=&quot;hibernate.format_sql&quot; value=&quot;true&quot; />
    </properties>
  </persistence-unit>
</persistence>

现在是我的问题:
当运行像这样的代码时:

src/main/java/mypackage/Main.java:

public class Main {

  public static void main(String[] args)
  {
    EntityManagerFactory emf = Persistence.createEntityManagerFactory(&quot;myPu&quot;);
    EntityManager em = emf.createEntityManager();

    em.getTransaction()
        .begin();
    try
    {
      // case 1: there is no contract
      ProcessDTO process = new ProcessDTO();
      process.setData(&quot;XYZ&quot;);
      em.merge(process);

      // case 2: there is a contract
      ContractDTO contract = new ContractDTO();
      contract.setData(&quot;abc&quot;);
      contract = em.merge(contract);

      ProcessDTO process2 = new ProcessDTO();
      process2.setContract(contract);
      process2.setData(&quot;def&quot;);

      em.merge(process2);
    }
    finally
    {
      em.getTransaction()
          .commit();
    }

    em.getTransaction()
        .begin();
    try
    {
      ProcessDTO process1 = new ProcessDTO();
      process1.setData(&quot;abc&quot;);
      em.merge(process1);

      ProcessDTO process2 = new ProcessDTO();
      process2.setData(&quot;def&quot;);
      em.merge(process2);
    }
    finally
    {
      em.getTransaction()
          .commit();
    }

    em.getTransaction()
        .begin();
    try
    {
      TypedQuery<ProcessDTO> query =
          em.createQuery(&quot;from ProcessDTO where data=&#39;abc&#39;&quot;, ProcessDTO.class);

      List<ProcessDTO> processes = query.getResultList();
      if (!processes.isEmpty())
      {
        ProcessDTO process = processes.get(0);
        process.setData(&quot;abcdef&quot;);
        process = em.merge(process);
      }

      TypedQuery<ProcessDTO> queryToDelete =
          em.createQuery(&quot;from ProcessDTO where data=&#39;def&#39;&quot;, ProcessDTO.class);
      List<ProcessDTO> processesToDelete = queryToDelete.getResultList();
      if (!processesToDelete.isEmpty())
      {
        ProcessDTO process = processesToDelete.get(0);

        em.remove(process);
      }
    }
    finally
    {
      em.getTransaction()
          .commit();
    }
  }
}

DB2 SQL错误:SQLCODE=-407,SQLSTATE=23502,SQLERRMC=TBSPACEID=3,TABLEID=24,COLNO=1,DRIVER=4.19.66
这个错误的原因是,JPA尝试执行类似于以下语句:

INSERT INTO Processes(CONTRACTIDX, DATA) values (NULL, &#39;XYZ&#39;);

这是数据库不允许的。

案例1:“没有合同”应该生成类似于以下内容:

INSERT INTO Processes(DATA) values (&#39;XYZ&#39;);

案例2:“有合同”应该生成类似于以下内容:

INSERT INTO Contracts(DATA) values (&#39;abc&#39;);
INSERT INTO Processes(CONTRACTIDX, DATA) values (<合同的ID值>, &#39;def&#39;);

相关问题:然后,我

英文:

I am using Java and Hibernate JPA (5.6.15.Final) to write an application to store/change/delete contracts and processing data.

  • two database tables in a IBM DB/2 database (simplified):
CREATE TABLE &quot;Schema1&quot;.&quot;Contracts&quot; (
&quot;IDX&quot; INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY, 
&quot;DATA&quot; varchar(255));
CREATE TABLE &quot;Schema1&quot;.&quot;Processes&quot; (
&quot;IDX&quot; INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY, 
&quot;CONTRACTIDX&quot; INTEGER NOT NULL DEFAULT 0, 
&quot;DATA&quot; varchar(255));

The database that is used, is already existing, it is very old and other software is using it also (for one of the apps, we do not even have source code), so we cannot easily make changes to the tables and field definitions.

The reason, why CONTRACTIDX may be 0 by default is, that it should be possible to process before there is a contract here (that means: we could also be processing, when we are in long-running contract negotiations, and we could be processing, when we already have a contract)
So valid values for CONTRACTIDX should be an existing contract id or 0 for no contract.

  • the entities:

src/main/java/mypackage/ProcessDTO.java

@Entity
@Table(schema = &quot;Schema1&quot;, name = &quot;Processes&quot;)
public class ProcessDTO
{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = &quot;IDX&quot;)
private Long id = null;
@ManyToOne
@JoinColumn(name = &quot;CONTRACTIDX&quot; /* , nullable=true or nullable=false: I&#39;ve already tried that... it has no effect on the problem */)
private ContractDTO contract = null;
@Column(name=&quot;DATA&quot;)
private String data;
// getters and setters
}

src/main/java/mypackage/ContractDTO.java

@Entity
@Table(schema = &quot;SCHEMA1&quot;, name = &quot;Contracts&quot;)
public class ContractDTO {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = &quot;IDX&quot;)
private Long id;
@Column(name=&quot;DATA&quot;)
private String data;
// getters and setters
}

src/main/resources/META-INF/persistence.xml:

&lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&gt;
&lt;persistence xmlns=&quot;http://java.sun.com/xml/ns/persistence&quot;
xmlns:xsi=&quot;http://www.w3.org/2001/XMLSchema-instance&quot;
xsi:schemaLocation=&quot;http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd&quot;
version=&quot;2.0&quot;&gt;
&lt;persistence-unit name=&quot;myPu&quot;&gt;
&lt;jta-data-source&gt;jdbc/myDb&lt;/jta-data-source&gt;
&lt;class&gt;mypackage.ProcessDTO&lt;/class&gt;
&lt;class&gt;mypackage.ContractDTO&lt;/class&gt;
&lt;properties&gt;
&lt;property name=&quot;hibernate.dialect&quot; value= &quot;org.hibernate.dialect.DB2Dialect&quot; /&gt;
&lt;property name=&quot;hibernate.show_sql&quot; value=&quot;true&quot; /&gt;
&lt;property name=&quot;hibernate.format_sql&quot; value=&quot;true&quot; /&gt;
&lt;/properties&gt;
&lt;/persistence-unit&gt;
&lt;/persistence&gt;

Now comes my problem:
When running this with code like this:

src/main/java/mypackage/Main.java:

public class Main {
public static void main(String[] args)
{
EntityManagerFactory emf = Persistence.createEntityManagerFactory(&quot;myPu&quot;);
EntityManager em = emf.createEntityManager();
em.getTransaction()
.begin();
try
{
// case 1: there is no contract
ProcessDTO process = new ProcessDTO();
process.setData(&quot;XYZ&quot;);
em.merge(process);
// case 2: there is a contract
ContractDTO contract = new ContractDTO();
contract.setData(&quot;abc&quot;);
contract = em.merge(contract);
ProcessDTO process2 = new ProcessDTO();
process2.setContract(contract);
process2.setData(&quot;def&quot;);
em.merge(process2);
}
finally
{
em.getTransaction()
.commit();
}
em.getTransaction()
.begin();
try
{
ProcessDTO process1 = new ProcessDTO();
process1.setData(&quot;abc&quot;);
em.merge(process1);
ProcessDTO process2 = new ProcessDTO();
process2.setData(&quot;def&quot;);
em.merge(process2);
}
finally
{
em.getTransaction()
.commit();
}
em.getTransaction()
.begin();
try
{
TypedQuery&lt;ProcessDTO&gt; query =
em.createQuery(&quot;from ProcessDTO where data=&#39;abc&#39;&quot;, ProcessDTO.class);
List&lt;ProcessDTO&gt; processes = query.getResultList();
if (!processes.isEmpty())
{
ProcessDTO process = processes.get(0);
process.setData(&quot;abcdef&quot;);
process = em.merge(process);
}
TypedQuery&lt;ProcessDTO&gt; queryToDelete =
em.createQuery(&quot;from ProcessDTO where data=&#39;def&#39;&quot;, ProcessDTO.class);
List&lt;ProcessDTO&gt; processesToDelete = queryToDelete.getResultList();
if (!processesToDelete.isEmpty())
{
ProcessDTO process = processesToDelete.get(0);
em.remove(process);
}
}
finally
{
em.getTransaction()
.commit();
}
}
}

DB2 SQL Error: SQLCODE=-407, SQLSTATE=23502, SQLERRMC=TBSPACEID=3, TABLEID=24, COLNO=1, DRIVER=4.19.66
The reason for this error is, that JPA tries a statement like this:

INSERT INTO Processes(CONTRACTIDX, DATA) values (NULL, &#39;XYZ&#39;);

which the database doesn't allow.

Case 1: 'there is no contract' should generate something like

INSERT INTO Processes(DATA) values (&#39;XYZ&#39;);

Case 2: 'there is a contract' should generate something like

INSERT INTO Contracts(DATA) values (&#39;abc&#39;);
INSERT INTO Processes(CONTRACTIDX, DATA) values (&lt;the id value of that contract&gt;, &#39;def&#39;);

A related question: I then need a solution for the problem of processes, that have CONTRACTIX==0.
They should be accepted as if they were NULL;

Complete Stacktrace of Case 1:

Exception in thread &quot;main&quot; javax.persistence.PersistenceException: org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:154)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:181)
at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:188)
at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:840)
at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:816)
at de.continentale.mvp.jpatest001.Main.main(Main.java:39)
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
at org.hibernate.exception.internal.SQLExceptionTypeDelegate.convert(SQLExceptionTypeDelegate.java:59)
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:37)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:113)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:99)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:200)
at org.hibernate.dialect.identity.GetGeneratedKeysDelegate.executeAndExtract(GetGeneratedKeysDelegate.java:58)
at org.hibernate.id.insert.AbstractReturningDelegate.performInsert(AbstractReturningDelegate.java:43)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3279)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3914)
at org.hibernate.action.internal.EntityIdentityInsertAction.execute(EntityIdentityInsertAction.java:84)
at org.hibernate.engine.spi.ActionQueue.execute(ActionQueue.java:645)
at org.hibernate.engine.spi.ActionQueue.addResolvedEntityInsertAction(ActionQueue.java:282)
at org.hibernate.engine.spi.ActionQueue.addInsertAction(ActionQueue.java:263)
at org.hibernate.engine.spi.ActionQueue.addAction(ActionQueue.java:317)
at org.hibernate.event.internal.AbstractSaveEventListener.addInsertAction(AbstractSaveEventListener.java:329)
at org.hibernate.event.internal.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:286)
at org.hibernate.event.internal.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:192)
at org.hibernate.event.internal.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:122)
at org.hibernate.event.internal.DefaultMergeEventListener.saveTransientEntity(DefaultMergeEventListener.java:273)
at org.hibernate.event.internal.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:246)
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:178)
at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:74)
at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:107)
at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:829)
... 2 more
Caused by: com.ibm.db2.jcc.am.SqlIntegrityConstraintViolationException: DB2 SQL Error: SQLCODE=-407, SQLSTATE=23502, SQLERRMC=TBSPACEID=2, TABLEID=265, COLNO=1, DRIVER=4.19.66
at com.ibm.db2.jcc.am.kd.a(kd.java:743)
at com.ibm.db2.jcc.am.kd.a(kd.java:66)
at com.ibm.db2.jcc.am.kd.a(kd.java:135)
at com.ibm.db2.jcc.am.fp.c(fp.java:2788)
at com.ibm.db2.jcc.am.fp.a(fp.java:2236)
at com.ibm.db2.jcc.t4.bb.o(bb.java:908)
at com.ibm.db2.jcc.t4.bb.j(bb.java:267)
at com.ibm.db2.jcc.t4.bb.d(bb.java:55)
at com.ibm.db2.jcc.t4.p.c(p.java:44)
at com.ibm.db2.jcc.t4.vb.j(vb.java:157)
at com.ibm.db2.jcc.am.fp.nb(fp.java:2231)
at com.ibm.db2.jcc.am.gp.b(gp.java:4542)
at com.ibm.db2.jcc.am.gp.mc(gp.java:815)
at com.ibm.db2.jcc.am.gp.executeUpdate(gp.java:789)
at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:197)
... 21 more

Maven dependencies from pom.xml:

		&lt;dependency&gt;
&lt;groupId&gt;jakarta.enterprise&lt;/groupId&gt;
&lt;artifactId&gt;jakarta.enterprise.cdi-api&lt;/artifactId&gt;
&lt;version&gt;2.0.2&lt;/version&gt;
&lt;scope&gt;compile&lt;/scope&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;jakarta.inject&lt;/groupId&gt;
&lt;artifactId&gt;jakarta.inject-api&lt;/artifactId&gt;
&lt;version&gt;1.0&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;jakarta.persistence&lt;/groupId&gt;
&lt;artifactId&gt;jakarta.persistence-api&lt;/artifactId&gt;
&lt;version&gt;2.2.3&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.hibernate&lt;/groupId&gt;
&lt;artifactId&gt;hibernate-core&lt;/artifactId&gt;
&lt;version&gt;5.6.15.Final&lt;/version&gt;
&lt;scope&gt;compile&lt;/scope&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;jakarta.transaction&lt;/groupId&gt;
&lt;artifactId&gt;jakarta.transaction-api&lt;/artifactId&gt;
&lt;version&gt;1.3.3&lt;/version&gt;
&lt;scope&gt;compile&lt;/scope&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.jboss.narayana.jta&lt;/groupId&gt;
&lt;artifactId&gt;narayana-jta&lt;/artifactId&gt;
&lt;version&gt;5.12.7.Final&lt;/version&gt;
&lt;/dependency&gt;

Java: openjdk version "1.8.0_222"

答案1

得分: 0

我找到了一个解决方案...但不太喜欢它...有没有更好(更短,更清晰)的方法?

我修改了我的 ProcessDTO:

@Entity
@Table(schema = "Schema1", name = "Processes")
public class ProcessDTO
{
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  @Column(name = "IDX")
  private Long id = null;

  @ManyToOne
  @NotFound(action = NotFoundAction.IGNORE)
  @JoinColumn(name = "CONTRACTIDX",
      insertable = false,
      updatable = false,
      foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
  private ContractDTO contract = null;

  @Column(name = "CONTRACTIDX")
  private Long contractId = null;

  @Column(name = "DATA")
  private String data;

  // getters and setters

  @PrePersist
  @PreUpdate
  void beforeSave()
  {
    if (contract != null)
    {
      contractId = contract.getId();
    }
    else
    {
      contractId = 0L;
    }
  }

  @PostLoad
  void afterLoad()
  {
    if (contractId == 0L)
    {
      contractId = 0L;
      contract = null;
    }
  }
}

还有一个问题是,我需要使用 Hibernate 的原生注解:NotFound 和 NotFoundAction...

我更喜欢使用 JPA 接口的方式...

英文:

I found a solution... but don't like it very much... is there a better (shorter, clearer) one?

I changed my ProcessDTO:

@Entity
@Table(schema = &quot;Schema1&quot;, name = &quot;Processes&quot;)
public class ProcessDTO
{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = &quot;IDX&quot;)
private Long id = null;
@ManyToOne
@NotFound(action = NotFoundAction.IGNORE)
@JoinColumn(name = &quot;CONTRACTIDX&quot;,
insertable = false,
updatable = false,
foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
private ContractDTO contract = null;
@Column(name = &quot;CONTRACTIDX&quot;)
private Long contractId = null;
@Column(name = &quot;DATA&quot;)
private String data;
// getters and setters
@PrePersist
@PreUpdate
void beforeSave()
{
if (contract != null)
{
contractId = contract.getId();
}
else
{
contractId = 0L;
}
}
@PostLoad
void afterLoad()
{
if (contractId == 0L)
{
contractId = 0L;
contract = null;
}
}
}

One more 'bad thing' on it is, that I need hibernate-native annotations: NotFound and NotFoundAction...

I would prefer something from the jpa-interface...

huangapple
  • 本文由 发表于 2023年4月13日 23:18:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/76007162.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定