I am using hibernate and doing a simple query to select all of a given entity. I only want the entity and not its relations. However hibernate does fetch the relations and the relations of the relations, in a n+1 manner.
It's a Spring Boot application and written in Kotlin.
The relations are annotated with FetchType.LAZY and the query i'm doing looks like so:
entityManager.createQuery("SELECT a FROM Apartment a")
.resultList
This is the only code that i am calling and i do NOT even return the results from the controller, so it is not some serialization library that calls the lazy properties.
These are the entities:
#Entity
class Apartment(
#Id
private val id: String,
private val heading: String,
#ManyToOne(fetch = LAZY)
private val building: Building
)
#Entity
class Building(
#Id
private val id: String,
private val heading: String,
#ManyToOne(fetch = FetchType.LAZY)
private val owner: User
)
#Entity
#Table(name="users")
class User(
#Id
private val id: String,
private val email: String
)
I have turned on hibernate query logging, and when i run the query above it looks like:
Fetch all apartments
Fetch buildings for apartment 1
Fetch users for building 1
fetch buildings for apartment 2
fetch users for building 2
....
I have created a git repository that reproduces the problem: https://github.com/Herlevsen/hibernate-lazy-fetch-repoduction
It comes setup with a docker-compose file that launches a postgres database, and the application automatically creates the schema and creates some dummy data on startup. So it's fairly easy to get up and running.
I'm really hoping someone can tell me what's going on.
Thanks!
You have to open the entity classes. Hibernate requires open classes to create proxies to support LAZY - ManyToOne fields:
#Entity
open class Apartment(
#Id
private val id: String,
private val heading: String,
#ManyToOne(fetch = LAZY)
private val building: Building
)
#Entity
open class Building(
#Id
private val id: String,
private val heading: String,
#ManyToOne(fetch = FetchType.LAZY)
private val owner: User
)
#Entity
#Table(name="users")
open class User(
#Id
private val id: String,
private val email: String
)
You can also use the all-open-Plugin.
Related
Firstly, I'm new to micronaut and kotlin.
I'm trying to convert a data class that I receive as a request body to my API. I need to persist data from this json into a Postgres table. I wasn't able to add #field annotation to a #MappedEntity data class to validate empty fields with a message. Hence, I decided to use a normal data class to parse the request body and then create a new object of the #MappedEntity class to run the save query.
The issue:
I have a #GeneratedValue Id column in my mapped Entity class. When I try to create a new object of this class, I'm not able to leave the Id column blank.
Controller:
#Post("/contribute")
#Status(HttpStatus.CREATED)
fun processIndividualContribution(
#Valid #Body individualContributionRequestTO: IndividualContributionRequestTO
): Mono<IndividualContributionDTO>
Request Body Data CLass:
#JsonIgnoreProperties(ignoreUnknown = true)
data class IndividualContributionRequestTO(
#field: NotEmpty(message = "Transaction Id cannot be null") val transactionId: String,
#field: NotEmpty(message = "First Name can't be null") val firstName: String,
val lastName: String? = null,
#field: NotEmpty(message = "Email address can't be null") val emailAddress: String,
#field: NotEmpty(message = "Individual Contribution can't be null") val contribution: String,
)
{
fun toPurchasedDataEntity(individualContributionRequestTO: IndividualContributionRequestTO): PurchaseDataAll{
return PurchaseDataAll(
purchaserFirstName = individualContributionRequestTO.firstName,
purchaserLastName = individualContributionRequestTO.lastName,
purchaserEmail = individualContributionRequestTO.emailAddress,
contribution = individualContributionRequestTO.contribution
)
}
}
Mapped Entity Class:
#MappedEntity
#Table(name="purchased_all")
data class PurchaseDataAll (
#Id
#GeneratedValue
#Column(name = "purchase_id")
val purchaseId: Int,
#Column(name = "transaction_id")
val transactionId: String,
#Column(name = "purchaser_first_name")
val purchaserFirstName: String,
#Column(name = "purchaser_last_name")
val purchaserLastName: String? = null,
#Column(name = "purchaser_email")
val purchaserEmail: String,
#Column(name = "contribution")
val contribution: String,
#DateCreated
#Column(name = "created_ts")
var createdTs: LocalDateTime? = null,
#DateUpdated
#Column(name = "updated_ts")
var updatedTs: LocalDateTime? = null
)
The function toPurchasedDataEntity doesn't compile due to the missing purchaseId field in the returned object.
Is there a way I can parse the request body data class to the mapped entity class by ignoring the auto generated field?
In Kotlin, you'll have to prefix annotations witn field: like shown below. I also changed def for purchaseId so you don't have to specify it when mapping from view class (DTO) to entity class.
IMHO, I think it's a good approach to separate entity classes from view classes as you've done in your question.
#MappedEntity
#Table(name="purchased_all")
data class PurchaseDataAll (
#field:Id
#field:GeneratedValue
#field:Column(name = "purchase_id")
var purchaseId: Int? = null,
#field:Column(name = "transaction_id")
val transactionId: String,
#field:Column(name = "purchaser_first_name")
val purchaserFirstName: String,
#field:Column(name = "purchaser_last_name")
val purchaserLastName: String? = null,
#field:Column(name = "purchaser_email")
val purchaserEmail: String,
#field:Column(name = "contribution")
val contribution: String,
#field:DateCreated
#field:Column(name = "created_ts")
var createdTs: LocalDateTime? = null,
#field:DateUpdated
#field:Column(name = "updated_ts")
var updatedTs: LocalDateTime? = null
)
I'm trying to save an entity that has a JsonNode attribute and has to be converted to jsonb to be stores in a postgres database.
This is the database table:
CREATE TABLE IF NOT EXISTS configuration_data (
id SERIAL NOT NULL PRIMARY KEY,
namespace_name TEXT NOT NULL,
dimension_id TEXT NOT NULL,
dimension_value TEXT,
path TEXT,
data JSONB,
version BIGINT DEFAULT 0
);
This is the entity:
#MappedEntity(value = "configuration_data")
data class ConfigurationDataEntity(
#field:Id #GeneratedValue
val id: Long,
#MappedProperty("namespace_name")
val namespaceName: String,
#MappedProperty("dimension_id")
val dimensionId: String,
#MappedProperty("dimension_value")
val dimensionValue: String,
#MappedProperty("path")
val path: String,
#MappedProperty("data")
val data: JsonNode,
#MappedProperty("version")
val version: Long,
)
And I'm trying to save an entity like that into the repository:
val entity = ConfigurationDataEntity(1L, coordinates.namespace, "acc", "abc", "path", data, 1L)
repository.save(entity)
The error I'm getting is the following:
Caused by: io.micronaut.data.exceptions.DataAccessException: Error executing PERSIST: Unable to set PreparedStatement value: Can't infer the SQL type to use for an instance of com.fasterxml.jackson.databind.node.ObjectNode. Use setObject() with an explicit Types value to specify the type to use.
I imagine it is because postgres can not store a jsonNode in a jsonb object, but I don't know how to convert a JsonNode to jsonb.
Thank you very much in advance :)
Worked adding this annotation to the field:
#field:TypeDef(type = DataType.JSON)
So the entity remains like:
#MappedEntity(value = "configuration_data")
data class ConfigurationDataEntity(
#field:Id #GeneratedValue
val id: Long,
#MappedProperty("namespace_name")
val namespaceName: String,
#MappedProperty("dimension_id")
val dimensionId: String,
#MappedProperty("dimension_value")
val dimensionValue: String,
#MappedProperty("path")
val path: String,
#MappedProperty("data")
**#field:TypeDef(type = DataType.JSON)**
val data: JsonNode,
#MappedProperty("version")
val version: Long,
)
I'm trying to follow the most idiomatic way to having a few fully tested DAO services.
I've got a few case classes such as the following:
case class Person (
id :Int,
firstName :String,
lastName :String
)
case class Car (
id :Int,
brand :String,
model :String
)
Then I've got a simple DAO class like this:
class ADao #Inject()(protected val dbConfigProvider: DatabaseConfigProvider) extends HasDatabaseConfigProvider[JdbcProfile] {
import driver.api._
private val persons = TableQuery[PersonTable]
private val cars = TableQuery[CarTable]
private val personCar = TableQuery[PersonCar]
class PersonTable(tag: Tag) extends Table[Person](tag, "person") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def firstName = column[String]("name")
def lastName = column[String]("description")
def * = (id, firstName, lastName) <> (Person.tupled, Person.unapply)
}
class CarTable(tag: Tag) extends Table[Car](tag, "car") {
def id = column[Int]("id", O.PrimaryKey, O.AutoInc)
def brand = column[String]("brand")
def model = column[String]("model")
def * = (id, brand, model) <> (Car.tupled, Car.unapply)
}
// relationship
class PersonCar(tag: Tag) extends Table[(Int, Int)](tag, "person_car") {
def carId = column[Int]("c_id")
def personId = column[Int]("p_id")
def * = (carId, personId)
}
// simple function that I want to test
def getAll(): Future[Seq[((Person, (Int, Int)), Car)]] = db.run(
persons
.join(personCar).on(_.id === _.personId)
.join(cars).on(_._2.carId === _.id)
.result
)
}
And my application.conf looks like:
slick.dbs.default.driver="slick.driver.PostgresDriver$"
slick.dbs.default.db.driver="org.postgresql.Driver"
slick.dbs.default.db.url="jdbc:postgresql://super-secrete-prod-host/my-awesome-db"
slick.dbs.default.db.user="myself"
slick.dbs.default.db.password="yolo"
Now by going through Testing with databases and trying to mimic play-slick sample project
I'm getting into so much trouble and I cannot seem to understand how to make my test use a different database (I suppose I need to add a different db on my conf file, say slick.dbs.test) but then I couldn't find out how to inject that inside the test.
Also, on the sample repo, there's some "magic" like Application.instanceCache[CatDAO] or app2dao(app).
Can anybody point me at some full fledged example of or repo that deals correctly with testing play and slick?
Thanks.
I agree it's confusing. I don't know if this is the best solution, but I ended up having a separate configuration file test.conf that specifies an in-memory database:
slick.dbs {
default {
driver = "slick.driver.H2Driver$"
db.driver = "org.h2.Driver"
db.url = "jdbc:h2:mem:play-test"
}
}
and then told sbt to use this when running the tests:
[..] javaOptions in Test ++= Seq("-Dconfig.file=conf/test.conf")
i am new in PlayFramework and Scala. I am using PlayFramework anorm for enable database connection and use embedded h2 database. When i insert the values into the table, the insertion done successfully. But when i fetch the data from database it generates and error as below:
[RuntimeException: Left(TypeDoesNotMatch(Cannot convert 25.50: class java.math.BigDecimal to Float for column ColumnName(USER_DETAIL.AGE,Some(AGE))))]
Following is my POJO:
case class UserDetail(
val id: Int,
val name: String,
val age: Float
)
Following is my controller:
def getUserDetail = Action{
val userDetail = UserDetail(13, "James", 25.9F);
var sql: SqlQuery = SQL("SELECT * FROM USER_DETAIL");
def users: List[UserDetail] = DB.withConnection { implicit connection =>
sql().map(row => UserDetail(row[Int]("id"), row[String]("name"), row[Float]("age"))).toList
}
println(">>>>>>>>>>>>>>>>>>>>: "+users)
Ok(Json.toJson(users));
}
Following is my table structure:
create table User_Detail(
id int NOT NULL PRIMARY KEY,
name varchar(45),
age decimal(20, 2)
)
I am also trying to replace decimal type with double in table, but it again generate the same error like above
[RuntimeException: Left(TypeDoesNotMatch(Cannot convert 25.50: class java.lang.Double to Float for column ColumnName(USER_DETAIL.AGE,Some(AGE))))
How about
case class UserDetail(
val id: Int,
val name: String,
val age: Double
)
edit:
And then use row[Double]("age") when reading it
I have a simple Entity class like;
case class Place(var name: String) extends Model with GeoLocPoint with HasGeoLoc with ContainsTime with HasId {
var number: String = _ // Building number on the street.
var placeGroupId: Long = _
var chainId: Long = _
#OneToOne
#JoinColumn(name = "logo_id")
var logo: Image = _
#OneToOne
#JoinColumn(name = "cover_id")
var cover: Image = _
...
}
And the image class is;
package models.images
import javax.persistence.Entity
import models.HasId
import models.places.placegroups.places.{Place, LinkedToPlace}
import play.api.libs.json._
import play.db.ebean.Model
import play.db.ebean.Model.Finder
/**
* Created by asheshambasta on 31/12/14.
*/
#Entity
case class Image(
filePath: String,
src: String,
format: String,
role: String // C: cover, L: logo, G: gallery
) extends Model with HasId with LinkedToPlace {
var caption: String = _
}
Now when a place is created, I'm attaching a default image to it;
newPlace.save
val imgDir = Play.current.configuration.getString("static.dir").getOrElse("/tmp")
val imgUrl = Play.current.configuration.getString("static.url.img").getOrElse("/static/img")
val imgType = Play.current.configuration.getString("img.default.type").getOrElse("image/jpeg")
val coverFile = Play.current.configuration.getString("img.default.cover.file").getOrElse("default-cover.jpg")
val logoFile = Play.current.configuration.getString("img.default.cover.file").getOrElse("default-cover.jpg")
val cover = new Image(imgDir + "/" + coverFile, imgUrl + "/" + coverFile, imgType, "C")
cover.place = newPlace
cover.save
val logo = new Image(imgDir + "/" + logoFile, imgUrl + "/" + logoFile, imgType, "L")
logo.place = newPlace
logo.save
newPlace.cover = cover
newPlace.logo = logo
newPlace.update
But then every time, I see that the images get persisted correctly with the right place_id but the place logo and the cover don't. They stay null.
This seems straight forward, and yet it doesn't work. Does anyone have any pointers to why?
There are some innacuracies in provided code. There is
logo.place = newPlace
but there is no place field in Image class.
There is also place_id column mentioned but I cannot see it in the code. But there are logo_id and cover_id columns. When you do one-to-one relation you should add join-column only on one side of this relation. So it should be only logo_id and cover_id columns on Place table. Adding only place_id column on Image table would not work because there would be two rows in Image table with the same place_id and this would be impossible to distinguish which should be mapped to logo field and which to cover field.
I made some corrections and simplifications to make this code compile and work.
Place.scala:
#Entity
class Place( var aId: Int, var aNumber: String) extends Model{
#Id
val id:Int=aId
#Column(name="number")
var number: String = aNumber
#OneToOne
#JoinColumn (name = "logo_id")
var logo: Image = _
#OneToOne
#JoinColumn(name = "cover_id")
var cover: Image = _
}
Image.class:
#Entity
#Table(name = "image")
class Image(var aId: Int, var aCaption: String) extends Model {
#Id
val id:Int=aId
#Column(name="caption")
var caption: String = aCaption
}
test method:
"EbeanTest" should {
"placeTest" in new WithApplication {
val newPlace = new Place(1, "aaa")
newPlace.save
val logo: Image = new Image(1, "111");
logo.save
val cover: Image = new Image(2, "222");
cover.save
newPlace.logo=logo
newPlace.cover=cover
newPlace.update
val cList = Ebean.find(classOf[Place]).findList()
for (
element <- cList
) println(element.id+" "+element.number+" "+element.logo.id+" "+element.cover.id)
}
}