2

I am trying to write a CRUD service using Exposed on Kotlin. I have a table with many-to-one reference. When I am trying to do an insert I am getting

java.lang.IllegalStateException: No transaction in context.

Here is table and entity

object Contacts : IntIdTable("ph_contact"){
    var firstName = varchar("first_name",255)
    var lastName = varchar("last_name",255)
    var phone = varchar("phone",4096)
    var email = varchar("email",4096)
    var user = reference("user",Users)
}

class ContactEntity (id: EntityID<Int>): IntEntity(id){
    companion object : IntEntityClass<ContactEntity>(Contacts)

    var firstName by Contacts.firstName
    var lastName by Contacts.lastName
    var phone by Contacts.phone
    var email by Contacts.email
    var user by UserEntity referencedOn Contacts.user


    fun toContact() = Contact(id.value,user.id.value,firstName,lastName,phone,email)

}


data class Contact(
    val id: Int,
    val userId: Int,
    val firstName: String,
    val lastName: String,
    val phone: String,
    val email: String
)

Here is service method for creation

fun createContact(contact: Contact) = transaction {

        ContactEntity.new {

           this.firstName=contact.firstName
           this.lastName=contact.lastName
           this.phone=contact.phone
           this.email=contact.email
           this.user= UserEntity[contact.userId]

        }
    }

The error is thrown when I call toContact() method after saving the entity

var contact = contactEntity.toContact()

What is the correct way to perform creation of such entity?

Alex Bondar
  • 1,167
  • 4
  • 18
  • 35

1 Answers1

5

You need to call your toContact method inside of the transaction of your create method.

The nested user record of the record is only accessible lazily from inside the transaction.

I approach this by making any Data Access layer interfaces use my own data class. The Exposed records and tables stay private below that layer. Do not let transactional entities leak out above it.

Example below with a nested User to illustrate:

object Users : IntIdTable("ph_users") {
    var otherUserData = varchar("other_user_data", 255)
}

class UserEntity(id: EntityID<Int>) : IntEntity(id) {
    companion object : IntEntityClass<UserEntity>(Users)

    var otherUserData by Users.otherUserData


    fun toUser() = User(id.value, otherUserData)

}

object Contacts : IntIdTable("ph_contact") {
    var firstName = varchar("first_name", 255)
    var lastName = varchar("last_name", 255)
    var phone = varchar("phone", 4096)
    var email = varchar("email", 4096)
    var user = reference("user", Users)
}

class ContactEntity(id: EntityID<Int>) : IntEntity(id) {
    companion object : IntEntityClass<ContactEntity>(Contacts)

    var firstName by Contacts.firstName
    var lastName by Contacts.lastName
    var phone by Contacts.phone
    var email by Contacts.email
    var user by UserEntity referencedOn Contacts.user

    fun toContact() = Contact(id.value, user.toUser(), firstName, lastName, phone, email)
}


data class Contact(
    val id: Int,
    val user: User,
    val firstName: String,
    val lastName: String,
    val phone: String,
    val email: String
)

data class User(
    val id: Int,
    val otherUserData: String
)

class ContactsRepo {

    fun createContact(contact: Contact): Contact = transaction {
        ContactEntity.new {
            this.firstName = contact.firstName
            this.lastName = contact.lastName
            this.phone = contact.phone
            this.email = contact.email
            this.user = UserEntity[contact.user.id]

        }.toContact()
    }

}

EDIT: The alternative is to not have the User record as referenced by, but rather just keep it as a user id? But possibly you need this nested record in other queries.

Laurence
  • 1,556
  • 10
  • 13