در نگاه اول کاربرد استفاده از Annotation های NotNull@ و (Column(nullable = false@ یکسان بنظر می آیند و میتوان بجای یکدیگر استفاده شوند ولی اینگونه نیست.
حتی هنگام استفاده در یک JPA Entity هر دو annotation از ذخیره مقادیر Null در دیتابیس جلوگیری میکند ولی تفاوت های چشمگیری دارند که در ادامه به آنها میپردازیم
Entity نمونه :
@Entity public class Item { @Id @GeneratedValue private Long id; private BigDecimal price; }
NotNull@:
این annotation برای Bean Validation تعریف شده است و استفاده از آن فقط در کلاس Entity محدود نشده است
بیاییم یکی از فیلد های Entity را NotNull@ کنیم :
@Entity public class Item { @Id @GeneratedValue private Long id; @NotNull private BigDecimal price; }
و سعی کنیم یک Entity با مقدار Null برای فیلد NotNull@ شده ذخیره کنیم :
@SpringBootTest public class ItemIntegrationTest { @Autowired private ItemRepository itemRepository; @Test public void shouldNotAllowToPersistNullItemsPrice() { itemRepository.save(new Item()); } }
و خروجی هایبرنیت اینگونه خواهد بود :
2019-11-14 12:31:15.070 ERROR 10980 --- [ main] o.h.i.ExceptionMapperStandardImpl : HHH000346: Error during managed flush [Validation failed for classes [com.baeldung.h2db.springboot.models.Item] during persist time for groups [javax.validation.groups.Default,] List of constraint violations:[ ConstraintViolationImpl{interpolatedMessage='must not be null', propertyPath=price, rootBeanClass=class com.baeldung.h2db.springboot.models.Item, messageTemplate='{javax.validation.constraints.NotNull.message}'}]] (...) Caused by: javax.validation.ConstraintViolationException: Validation failed for classes [com.baeldung.h2db.springboot.models.Item] during persist time for groups [javax.validation.groups.Default,] List of constraint violations:[ ConstraintViolationImpl{interpolatedMessage='must not be null', propertyPath=price, rootBeanClass=class com.baeldung.h2db.springboot.models.Item, messageTemplate='{javax.validation.constraints.NotNull.message}'}]
همانطور که میبینید خطای javax.validation.ConstraintViolationException پرتاب شده
** نکته قابل توجه این است که Hibernate دیتا را چون Validate نشده ذخیره نکرده است و مراحل اعتبار سنجی قبل از کوئری SQL انجام و خطا پرتاب شده است
ساخت Schema :
وقتی هایبرنیت میخواهد اسکیمای دیتابیس را بسازد نیاز داریم که تنظیماتی را ست کنیم :
در application.properties :
spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.show-sql=true
بعد از استارت شدن برنامه، عبارت زیر در کنسول قابل مشاهده است :
create table item ( id bigint not null, price decimal(19,2) not null, primary key (id) )
میبینیم که عبارت not null برای column در نظر گرفته شده است چون از نظر هایبرنیت در یک کلاس Entity وقتی فیلدی NotNull@ باشد اینطور برداشت میکند که ما میخواهیم آن فیلد در دیتابیس هم Not Null باشد
اگر بخواهیم هایبرنیت برای فیلد های NotNull@ تصمیم نگیرد کافی است عبارت hibernate.validator.apply_to_ddl را false کنیم به این صورت :
spring.jpa.hibernate.ddl-auto=create-drop spring.jpa.show-sql=true spring.jpa.properties.hibernate.validator.apply_to_ddl=false
و لاگ آن :
create table item ( id bigint not null, price decimal(19,2), primary key (id) )
که دیگر آن فیلد Not Null نیست
(Column(nullable = false@ :
این annotation در Java Persistence API تعریف شده است که کاربردش در استفاده در عبارات DDL برای ساخت Database Schema است
حال بیاییم Entity را با این annotation در نظر بگیریم :
@Entity public class Item { @Id @GeneratedValue private Long id; @Column(nullable = false) private BigDecimal price; }
و سعی میکنیم این Entity را با مقدار Null برای price ذخیره کنیم :
@SpringBootTest public class ItemIntegrationTest { @Autowired private ItemRepository itemRepository; @Test public void shouldNotAllowToPersistNullItemsPrice() { itemRepository.save(new Item()); } }
خروجی لاگ برای ساخت دیتابیس همراه با عبارت not null برای فیلد price :
Hibernate: create table item ( id bigint not null, price decimal(19,2) not null, primary key (id) )
نتیجه کوئری :
Hibernate: insert into item (price, id) values (?, ?) 2019-11-14 13:23:03.000 WARN 14580 --- [main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 23502, SQLState: 23502 2019-11-14 13:23:03.000 ERROR 14580 --- [main] o.h.engine.jdbc.spi.SqlExceptionHelper : NULL not allowed for column "PRICE"
همانطور که مشاهده میکنیم کوئری insert ساخته شده و پیام خطا از سمت دیتابیس ارسال شده است و این یعنی کوئری در دیتابیس اجرا شده و بدون اینکه در سمت کد ما null نبودن فیلد تست شود دیتابیس جلوی ذخیره شدن آن را گرفته است و کوئری به دیتابیس ارسال شده است
اعتبار سنجی :
اکثر منابع استفاده از (Column(nullable = false@ را برای عبارات DDL در نظر میگیرند ولی هایبرنیت قادر است فیلد های مشخص شده با (Column(nullable = false@ را اعتبار سنجی کند و برای فعال شدن این ویژگی نیاز است مقدار hibernate.check_nullability را true کنیم :
spring.jpa.show-sql=true spring.jpa.properties.hibernate.check_nullability=true
و اگر دوباره کد قبل از اجرا کنیم میتوانیم ببینیم که قبل از ارسال کوئری به دیتابیس اعتبار سنجی صورت میگیرد :
org.springframework.dao.DataIntegrityViolationException: not-null property references a null or transient value : com.baeldung.h2db.springboot.models.Item.price; nested exception is org.hibernate.PropertyValueException: not-null property references a null or transient value : com.baeldung.h2db.springboot.models.Item.price
و در صورت نا معتبر بودن خطای org.hibernate.PropertyValueException پرتاب خواهد شد