私は、主キーとしてDateTime列を持つテーブルがあります:
USE [idatest]
GO
CREATE TABLE [dbo].[DatesTbl](
[creationDate] [datetime] NOT NULL
CONSTRAINT [PK_DatesTbl] PRIMARY KEY CLUSTERED
(
[creationDate] ASC
))
GO
私は日時が(ミリ秒)のための3桁の数字を保持しているためentityManager.merge I GET複製、PK違反をしているが、hibernetてるときは、DATETIME2に変換(ミリ秒)のための7桁の数字を保持します。Javaコードで、私はmilsecのために10桁の数字を保持しているLocaDatetimeを使用しています。
私は解決策はで説明しようとしている休止MSSQL DATETIME2マッピングが、それは動作しません:のpom.xml:のようなJavaコードのルックスを
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.3.RELEASE</version>
</parent>
<groupId>com.example</groupId>
<artifactId>spring-jap-test</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>7.0.0.jre8</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
DatesTblクラス
@Entity
@NoArgsConstructor
@AllArgsConstructor
public class DatesTbl {
@Column(columnDefinition = "DATETIME", nullable = false)
@Id
private LocalDateTime creationDate;
}
メインクラス
@EnableTransactionManagement
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
EntityManagerFactory entityManagerFactory = context.getBean(EntityManagerFactory.class);
final EntityManager entityManager = entityManagerFactory.createEntityManager();
final LocalDateTime creationDate = LocalDateTime.of(2018, 12, 26, 8, 10, 40, 340);
entityManager.getTransaction().begin();
final DatesTbl datesTbl = entityManager.merge(new DatesTbl(creationDate));
entityManager.getTransaction().commit();
System.out.println("test");
}
@Bean
@Primary
public DataSource getDataSource() {
SQLServerDataSource ds = null;
try {
ds = new SQLServerDataSource();
ds.setServerName("localhost");
ds.setDatabaseName("idatest");
ds.setIntegratedSecurity(true);
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
return ds;
}
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
hibernateJpaVendorAdapter.setShowSql(true);
hibernateJpaVendorAdapter.setGenerateDdl(true);
hibernateJpaVendorAdapter.setDatabase(Database.SQL_SERVER);
return hibernateJpaVendorAdapter;
}
@Bean
public LocalContainerEntityManagerFactoryBean abstractEntityManagerFactoryBean(
JpaVendorAdapter jpaVendorAdapter) {
Properties properties = new Properties();
properties.setProperty(FORMAT_SQL, String.valueOf(true));
properties.setProperty(SHOW_SQL, String.valueOf(true));
properties.setProperty(DIALECT, ModifiedSQLServerDialect.class.getTypeName());
LocalContainerEntityManagerFactoryBean localContainerEntityManagerFactoryBean =
new LocalContainerEntityManagerFactoryBean();
localContainerEntityManagerFactoryBean.setDataSource(getDataSource());
localContainerEntityManagerFactoryBean.setJpaVendorAdapter(jpaVendorAdapter);
localContainerEntityManagerFactoryBean.setJpaProperties(properties);
localContainerEntityManagerFactoryBean.setPackagesToScan("enteties");
return localContainerEntityManagerFactoryBean;
}
@Bean
public PlatformTransactionManager platformTransactionManager(EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
}
public class ModifiedSQLServerDialect extends SQLServer2012Dialect {
public ModifiedSQLServerDialect () {
super();
registerColumnType(Types.TIMESTAMP, "timestamp");
registerColumnType(Types.DATE, "timestamp");
registerColumnType(Types.TIME, "timestamp");
registerHibernateType(Types.TIMESTAMP, "timestamp");
registerHibernateType(Types.DATE, "timestamp");
registerHibernateType(Types.TIME, "timestamp");
}
}
それでも私は、SQLServerのプロファイラで参照してください。
exec sp_executesql N'select datestbl0_.creationDate as creation1_0_0_ from DatesTbl datestbl0_ where datestbl0_.creationDate=@P0 ',N'@P0 `datetime2`','2018-12-26 08:10:40.0000003'
ソリューションと間違っては何ですか?
説明
問題は、MSSQL-JDBC(バージョン4.xおよび6.x)での問題に関連しているPreparedStatement.setTimestamp(インデックス、タイムスタンプ、カレンダー)は、データ型変換の問題を持って、常に送信され、LocalDateTime
とパラメータをdatetime2
無視して(SQLサーバーへのデータ・タイプテーブルの列型)。精度の異なるによるdatetime
(0.00333sec)及びdatetime2
(100ナノ秒)、及びdatetime
PK、Hibernateはこの場合に誤って動作するように使用されます。
私たちは、メインプログラムを実行すると、creationDate
値が持つている10:2018年12月26日08 40.000000340を、その値は次のように保存されている2018年12月26日08:40.000:10 HibernateはDB内の同じキーを持つという記録が見つからないとして、DBに。我々は再びメインプログラムを実行すると、同じキーを持つすべてのレコードがある場合は、使用して、最初のチェックを休止
08 2018-12-26 ''、DATETIME2 'P0 @ 'N' 'をdatestbl0_.creationDate=@P0 DatesTblのdatestbl0_からcreation1_0_0_としてdatestbl0_.creationDateを選択':10:40.0000003'
SQL Serverがアップキャストと思われるdatetime
にテーブルの値をdatetime2
比較のためにと、何のレコードが返されません。そこで再び、レコード挿入し休止状態、および主キー違反につながります。
回避策
ヴラッド・ミホールセアによって示唆されるように、PKとしてDATETIME列を使用するのは良いアイデアではありません。
しかし、我々はまだ必要があるとdatetime
PK、次の回避策として、列が動作するはずです。この問題を解決するための鍵は、間の比較を行うことであるdatetime
とdatetime2
trueを返します。これを達成するために、我々は切り捨てることができます/ラウンドdatetime2
値を対応するdatetime
値DBに渡す前に。メインプログラムに次の変更は、エラーなしでSQL Server 2012のExpressでテストされています。
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
EntityManagerFactory entityManagerFactory = context.getBean(EntityManagerFactory.class);
final EntityManager entityManager = entityManagerFactory.createEntityManager();
LocalDateTime creationDate0 = LocalDateTime.of(2018, 12, 26, 8, 10, 40, 341340340);
LocalDateTime creationDate3 = LocalDateTime.of(2018, 12, 26, 8, 10, 40, 343340340);
LocalDateTime creationDate7 = LocalDateTime.of(2018, 12, 26, 8, 10, 40, 346670340);
LocalDateTime creationDate10 = LocalDateTime.of(2018, 12, 26, 8, 10, 40, 349670340);
entityManager.getTransaction().begin();
final DatesTbl datesTbl0 = entityManager.merge(new DatesTbl(roundNanoSecForDateTime(creationDate0)));
final DatesTbl datesTbl3 = entityManager.merge(new DatesTbl(roundNanoSecForDateTime(creationDate3)));
final DatesTbl datesTbl7 = entityManager.merge(new DatesTbl(roundNanoSecForDateTime(creationDate7)));
final DatesTbl datesTbl10 = entityManager.merge(new DatesTbl(roundNanoSecForDateTime(creationDate10)));
entityManager.getTransaction().commit();
System.out.println("test");
}
private static LocalDateTime roundNanoSecForDateTime(LocalDateTime localDateTime) {
int nanoSec = localDateTime.getNano();
// The rounding is based on following results on SQL server 2012 express
// select cast(cast('2018-12-26 08:10:40.3414999' as datetime2) as datetime);
// 2018-12-26 08:10:40.340
// select cast(cast('2018-12-26 08:10:40.3415000' as datetime2) as datetime);
// select cast(cast('2018-12-26 08:10:40.3444999' as datetime2) as datetime);
// 2018-12-26 08:10:40.343
// select cast(cast('2018-12-26 08:10:40.3445000' as datetime2) as datetime);
// select cast(cast('2018-12-26 08:10:40.3484999' as datetime2) as datetime);
// 2018-12-26 08:10:40.347
// select cast(cast('2018-12-26 08:10:40.3485000' as datetime2) as datetime);
// 2018-12-26 08:10:40.350
int last7DigitOfNano = nanoSec - (nanoSec / 10000000) * 10000000;
int roundedNanoSec = 0;
if (last7DigitOfNano < 1500000) {
roundedNanoSec = nanoSec - last7DigitOfNano;
} else if (last7DigitOfNano < 4500000) {
roundedNanoSec = nanoSec - last7DigitOfNano + 3000000;
} else if (last7DigitOfNano < 8500000) {
roundedNanoSec = nanoSec - last7DigitOfNano + 7000000;
} else {
roundedNanoSec = nanoSec - last7DigitOfNano + 10000000;
}
System.out.println("Before Rounding" + nanoSec);
System.out.println("After Rounding" + roundedNanoSec);
return localDateTime.withNano(roundedNanoSec);
}
参考:
1. SQL ServerでのDateTime対DATETIME2
2. 日付と時刻のデータ型および関数(Transact-SQL)