-
Notifications
You must be signed in to change notification settings - Fork 2
Bkit 140 jpa #29
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Bkit 140 jpa #29
Conversation
BKIT-140
BKIT-140
case sensitivity when hibernate validates schema BKIT-140
BKIT-140
still needed for validations BKIT-140
| @ManyToOne(optional = false) | ||
| val user: User, | ||
| @Id @GeneratedValue(generator = "uuid2") @GenericGenerator(name = "uuid2", strategy = "uuid2") | ||
| val id: String? = null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
generally moved optional IDs to end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I slightly prefer a degree of isomorphism between the schema and the code, and like PKs first in a schema, but I can't what JPA will do in the schema anyway with regard to order (maybe it sorts keys to the top?).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fair point... i'll have a looksie as to what it actually does
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hibernate: drop table bookable if exists
Hibernate: drop table booking if exists
Hibernate: drop table location if exists
Hibernate: drop table user if exists
Hibernate: create table bookable (id varchar(255) not null, closed boolean not null, reason varchar(255) not null, name varchar(255) not null, location_id varchar(255) not null, primary key (id))
Hibernate: create table booking (id varchar(255) not null, end timestamp not null, start timestamp not null, subject varchar(255) not null, bookable_id varchar(255) not null, user_id varchar(255) not null, primary key (id))
Hibernate: create table location (id varchar(255) not null, name varchar(255) not null, time_zone varchar(255) not null, primary key (id))
Hibernate: create table user (id varchar(255) not null, external_id varchar(255) not null, family_name varchar(255) not null, given_name varchar(255) not null, primary key (id))
Hibernate: alter table bookable add constraint UK_6ietqnvn35wqiovo8qrmwk2ne unique (location_id, name)
Hibernate: alter table location add constraint UK_sahixf1v7f7xns19cbg12d946 unique (name)
Hibernate: alter table user add constraint UK_4eu2tvn9rj53a93fufx7ayr20 unique (external_id)
Hibernate: alter table bookable add constraint FK1dc19oypan7r3ry418vn2h5sc foreign key (location_id) references location
Hibernate: alter table booking add constraint FKtbhx4q4q4m1kv4muhynct3p81 foreign key (bookable_id) references bookable
Hibernate: alter table booking add constraint FKkgseyy7t56x7lkjgu3wah5s3t foreign key (user_id) references user
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so looks like it does the right thing i'd like to see from a schema perspective wrt order
| fun getBookable(@PathVariable("locationId") location: String, @PathVariable("bookableId") bookable: String): BookableResource { | ||
| locationRepository.getLocations().find { it.id == location } ?: throw LocationNotFound() | ||
| return BookableResource(bookableRepository.getAllBookables().find { it.id == bookable } ?: throw BookableNotFound()) | ||
| fun getBookable(@PathVariable("locationId") location: Location?, @PathVariable("bookableId") bookable: Bookable?): BookableResource { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
spring will automatically use the locationRepo to map locationId -> location object so we don't need our own dependency to it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Magic is cool. And scary and confusing. Oh well, can't have everything :-)
And this, I think, answers my question above, re "witchcraft" (?).
| @Id @GeneratedValue(generator = "uuid2") @GenericGenerator(name = "uuid2", strategy = "uuid2") | ||
| val id: String? = null | ||
| ) { | ||
| val name get() = "$givenName $familyName" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sort of a hack to reuse the User entity as the User response but it still seems "simple enough"...
| # generate-ddl: false | ||
| show-sql: true | ||
| hibernate: | ||
| ddl-auto: validate |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these settings might change once/if we let JPA manage the schema... for now still driven by schema.sql
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kind of fear us getting into a "deadlocked" deployment again if we fail to sync schema.sql and annotations and thus startup fails. You?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agree... hoping the existence of schema.sql is short term... next go around will eliminate it
| USER_ID VARCHAR(36) NOT NULL, | ||
| PRIMARY KEY (USER_ID), | ||
| EXTERNAL_USER_ID VARCHAR(128) NOT NULL, | ||
| CREATE TABLE user ( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
msyql & hibernate schema validation seems to be case sensitive, although nothing else seems to be...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Weird. I actually like lower-case table names in case of MySql case-sensitive installation (hate having to type capital letter table names in queries).
| PRIMARY KEY (USER_ID), | ||
| EXTERNAL_USER_ID VARCHAR(128) NOT NULL, | ||
| CREATE TABLE user ( | ||
| ID VARCHAR(36) NOT NULL, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
took the naming conventions from JPA/Hibernate instead of adding to JPA annotations
| "bookableId": "aab6d676-d3cb-4b9b-b285-6e63058aeda8", | ||
| "bookable": { | ||
| "id": "aab6d676-d3cb-4b9b-b285-6e63058aeda8" | ||
| }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@frostiebot @lizziesz one example of how the shape of responses change...generally if you had an id coming back now you have a whole object (we only validate the "id" but everything comes back...)...
we can have further discussions as to whether this is OK or whether we shouldn't do that... or whether we should replace ids w/ URIs etc etc
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This allows for flexibility in future and is better now before things harden more (IMO).
| "id": "aab6d676-d3cb-4b9b-b285-6e63058aeda8", | ||
| "locationId": "b1177996-75e2-41da-a3e9-fcdd75d1ab31", | ||
| "location": { | ||
| "id": "b1177996-75e2-41da-a3e9-fcdd75d1ab31" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@frostiebot @lizziesz ditto
| registry.addConverter(String::class.java, Booking::class.java) { id -> | ||
| when (id) { | ||
| BookingControllerMockMvcTests.booking.id -> BookingControllerMockMvcTests.booking | ||
| else -> null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this magic is needed to handle the id -> object "lookup" that spring does for us now... almost decided this was too "magic" and went back to what we had... thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Witchcraft. Burn it.
Just kidding! Too disruptive to change back away from it based on the work you've done here, IMO. But my brain still recoils to getFoo(Foo).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Much elegant. So better.
Thanks for your work on this!
My comments are mostly observations. See what you think, and merge away. Fire in the hole!
| return allBookings | ||
| .filter { booking -> | ||
| val timezone = bookableTimezones[booking.bookableId] ?: throw IllegalStateException("Encountered an incomplete booking: $booking. Not able to determine booking's timezone.") | ||
| val timezone = booking.bookable.location.timeZone |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed due to constraints guaranteeing presence?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes
| @GetMapping("/{id}") | ||
| fun getBooking(@PathVariable("id") bookingId: String, @AuthenticationPrincipal user: UserPrincipal): Booking = | ||
| bookingRepository.getAllBookings().find { it.id == bookingId }.let { maskSubjectIfOtherUser(it ?: throw BookingNotFound(), user) } | ||
| fun getBooking(@PathVariable("id") booking: Booking?, @AuthenticationPrincipal user: UserPrincipal): Booking = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What witchcraft is this? Am looking at the diffs in the browser, so probably just need context.
| @ManyToOne(optional = false) | ||
| val user: User, | ||
| @Id @GeneratedValue(generator = "uuid2") @GenericGenerator(name = "uuid2", strategy = "uuid2") | ||
| val id: String? = null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I slightly prefer a degree of isomorphism between the schema and the code, and like PKs first in a schema, but I can't what JPA will do in the schema anyway with regard to order (maybe it sorts keys to the top?).
| fun getBookable(@PathVariable("locationId") location: String, @PathVariable("bookableId") bookable: String): BookableResource { | ||
| locationRepository.getLocations().find { it.id == location } ?: throw LocationNotFound() | ||
| return BookableResource(bookableRepository.getAllBookables().find { it.id == bookable } ?: throw BookableNotFound()) | ||
| fun getBookable(@PathVariable("locationId") location: Location?, @PathVariable("bookableId") bookable: Bookable?): BookableResource { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Magic is cool. And scary and confusing. Oh well, can't have everything :-)
And this, I think, answers my question above, re "witchcraft" (?).
| "bookings" in expand ?: emptyList() -> | ||
| allBookings | ||
| .filter { booking -> booking.bookableId == bookable.id } | ||
| bookingRepository.findByBookable(bookable) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now we have an O(n^2) (or some variety of such) thing here, I think, WRT database calls. With the data we have today, no big deal, but someday ...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yess..... thanks for point that out... forgot to clean this up... will leave in for this pr and fix right after
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
although, maaaaaybe some hibernate caching (all in the same transaction) alleviates us from being really dumb about it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't complain. Really nice work here. I'm loving the reduction in code. Spring Data is pretty darn nice.
| # generate-ddl: false | ||
| show-sql: true | ||
| hibernate: | ||
| ddl-auto: validate |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kind of fear us getting into a "deadlocked" deployment again if we fail to sync schema.sql and annotations and thus startup fails. You?
| registry.addConverter(String::class.java, Booking::class.java) { id -> | ||
| when (id) { | ||
| BookingControllerMockMvcTests.booking.id -> BookingControllerMockMvcTests.booking | ||
| else -> null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Witchcraft. Burn it.
Just kidding! Too disruptive to change back away from it based on the work you've done here, IMO. But my brain still recoils to getFoo(Foo).
| } | ||
|
|
||
| @TestConfiguration | ||
| class InternalConfig { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any possibility of DRY?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you mean between tests? although they'd have to share test data assumptions...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Location's mapping is defined in multiple tests. Just wonder if the @TestConfiguration can be DRY for that Entity, for example. It's not a major thing -- the code is write once (in a few locations), and never revisit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be a stupid question, too. I'm really not familiar with Spring Data beyond the benefits, WRT repos, etc. The magical parts and implementation niceties are still a mystery.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i think it's for the test...although there mgiht be some ways to factor stuff out
Left todo: