I am newcomer.
How to migrate room database.
I tried to migrate using this instruction https://developer.android.com/training/data-storage/room/migrating-db-versions , but don't understand where should I use it.
I changed version from 1 to 2 and add column #ColumnInfo(name = "age") val age: Int
Could you help me to migrate it?
#Entity
data class User(
#PrimaryKey(autoGenerate = true) val uid: Int = 0,
#ColumnInfo(name = "first_name") val firstName: String?,
#ColumnInfo(name = "last_name") val lastName: String?,
#ColumnInfo(name = "age") val age: Int
)
#Dao
interface UserDao {
#Query("SELECT * FROM user")
fun getAll(): LiveData<List<User>>
#Query("SELECT * FROM user WHERE uid IN (:userIds)")
fun loadAllByIds(userIds: IntArray): List<User>
#Query(
"SELECT * FROM user WHERE first_name LIKE :first AND " +
"last_name LIKE :last LIMIT 1"
)
fun findByName(first: String, last: String): User
#Insert
fun insertAll(vararg users: User)
#Delete
fun delete(user: User)
}
#Database(entities = arrayOf(User::class), version = 2)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object {
private val mBD = AppDatabase
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE 'User' ADD COLUMN 'age' INTEGER DEFAULT 0")
}
}
}
}
Related
I have a Character-in-Category based Jpa relationship here:
// Character.kt
#Entity
class Character (
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
var name: String, //This one
#ManyToOne #JoinColumn(name = "category_name", referencedColumnName = "name")
var category: Category? = null,
var gender: Gender? = null
): Serializable {
enum class Gender {
MALE, FEMALE, OTHER
}
}
// Category.kt
#Entity
class Category (
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
var name: String? = null, //Also this one
#OneToMany(mappedBy = "category") #JsonIgnore
var characters: MutableSet<Character> = mutableSetOf()
) : Serializable
I have done some "findBy" queries by multiple columns before, but this is the first time that both of them have the same name and i suppose its not working because of that, how can i reach this without changing any of their property names?
// CharacterRepository.kt
#Repository
interface CharacterRepository : JpaRepository<Character, Long> {
fun findByNameAndCategory_Name(name: String, categoryName: String): Character
}
Edit: What is not working is that findBYNameAndCategory_Name, always returning Result must not be null as a EmptyResultDataAccessException despite the data actually exists in database.
Spring Data follows the path from the root entity in the implements (JpaRepository<Character, Long>): this should work:
Optional<Character> findByNameAndCategoryName(String name, String categoryName)
I don't use Kotlin, so this is Java syntax, but the same should apply for Kotlin: the method name matters.
I'm trying to integrate sorting with Pageable on joined fields with the use of #Query annotation from Spring Data.
1st interface's method (without #Query but with the Pageable) works like a charm. Same like when I'm fetching only one Employee with the #Query but instead of Pageable I'm using Optional<Employee> there (3rd method). But the fun begins when I try to put these two all together in one - it won't work anymore.
When I try to sort the data by name field it screams with this error:
Caused by: org.hibernate.QueryException: could not resolve property: name of: (....).model.employee.Employee
So the question is: how to tell spring to look for name in joined fields? How to do this with Spring Data?
I've already tried several things but they didn't work or I still don't know how to use them properly:
someone suggested to add countQuery to the #Query parameters so this corresponds somehow with the pagination (spring data jpa #query and pageable)
I've followed Baeldung's tutorial but this doesn't cover joins
Spring-Data FETCH JOIN with Paging is not working also suggested using countQuery but I'd prefer to stick to Page<Employee> rather than List<Employee>.
I'll leave some samples of the code below. Feel free to ask for update if I omitted something important.
// Employee
#Entity
#Table(name = "employee", schema = "emp")
#Data
#NoArgsConstructor
public class Employee {
private static final String SEQUENCE = "EMPLOYEE_SEQUENCE";
#Id
#SequenceGenerator(sequenceName = SEQUENCE, name = SEQUENCE, allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE)
private Long id;
#Column(name = "employee_number")
private String employeeNumber;
#Column
#Enumerated(EnumType.STRING)
private EmployeeStatus status;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#Audited(targetAuditMode = RelationTargetAuditMode.NOT_AUDITED)
#JoinColumn(name = "id_details")
private Details details;
// some other fields ...
}
// Details
#Entity
#Table(name = "details", schema = "emp")
#Data
#NoArgsConstructor
public class Details {
private static final String SEQUENCE = "DETAILS_SEQUENCE";
#Id
#SequenceGenerator(sequenceName = SEQUENCE, name = SEQUENCE, allocationSize = 1)
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = SEQUENCE)
private Long id;
private String name;
private String surname;
// some other fields ...
}
// EmployeeDTO
#NoArgsConstructor
#AllArgsConstructor
#Data
#Builder(toBuilder = true)
public class EmployeeDTO {
private Long id;
private String employeeNumber;
private String status;
private String name;
private String surname;
// some other fields ...
}
// EmployeeRepository
#Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
// 1st method
Page<Employee> findByStatus(EmployeeStatus status, Pageable pageable);
// 2nd method
#Query(value = "select e from Employee e join e.details where e.status = :status",
countQuery = "select count(*) from Employee e join e.details where e.status = :status")
Page<Employee> getEmployeeDetails(#Param("status") EmployeeStatus status, Pageable pageable);
// 3rd method
#Query("select e from Employee e join fetch e.details where e.id = :id")
Optional<Employee> findByIdWithDetails(Long id);
// ...
}
// EmployeeService
#Service
public class EmployeeService {
private final EmployeeRepository employeeRepository;
private final EntityDtoConverter entityDtoConverter;
#Autowired
public EmployeeService(EmployeeRepository employeeRepository, EntityDtoConverter entityDtoConverter) {
this.employeeRepository = employeeRepository;
this.entityDtoConverter = entityDtoConverter;
}
public EmployeeResponse getEmployeesByStatus(EmployeeStatus status, int pageSize, int pageIndex, Sort.Direction sortDirection, String sortColumn) {
Page<EmployeeDTO> employeePage = employeeRepository.findByStatus(status, PageRequest.of(pageIndex, pageSize, Sort.by(sortDirection, sortColumn)))
.map(entityDtoConverter::convertEmployeeBaseToDto);
return new EmployeeResponse(employeePage);
}
public EmployeeResponse getEmployeeDetails(EmployeeStatus status, int pageSize, int pageIndex, Sort.Direction sortDirection, String sortColumn) {
Page<EmployeeDTO> employeePage = employeeRepository.getEmployeeDetails(status, PageRequest.of(pageIndex, pageSize, Sort.by(sortDirection, sortColumn)))
.map(entityDtoConverter::convertToEmployeeWithDetailsDto);
return new EmployeeResponse(employeePage);
}
// ...
}
// EntityDtoConverter
#Component
public class EntityDtoConverter {
public EmployeeDTO convertEmployeeBaseToDto(Employee entity) {
return EmployeeDTO.builder()
.id(entity.getId())
.employeeNumber(entity.getEmployeeNumber())
.status(entity.getStatus())
.build();
}
public EmployeeDTO convertToEmployeeWithDetailsDto(Employee entity) {
return convertEmployeeBaseToDto(entity).toBuilder()
.name(entity.getDetails().getName())
.surname(entity.getDetails().getSurname())
.build();
}
// ...
}
EDIT:
This is one of the methods of my rest controller:
#GetMapping
public ResponseEntity<EmployeeResponse> getEmployeesByStatus(EmployeeStatus status, int pageSize, int pageIndex, String sortDirection, String sortColumn) {
try {
EmployeeResponse employeeResponse = employeeService.getEmployeesByStatus(status, pageSize, pageIndex, Sort.Direction.fromString(sortDirection), sortColumn);
return employeeResponse.getTotalElements().equals(0L) ? ResponseEntity.noContent().build() : ResponseEntity.ok(employeeResponse);
} catch (Exception e) {
log.error(ERROR_MESSAGE, e);
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}
Try below code.
Specification<Employee> joins = (employee, query, cb) -> {
Join<Employee, Detail> details = employee.join("details");
return cb.and(
employee.equal(employee.get("name", name)),
details.equal(details.get("name", detailName))
);
};
PageRequest pageRequest = new PageRequest(0, 2, new Sort(Sort.Direction.DESC, "name"));
Page<Employee> customerPage = employeeRepository.findAll(joins, pageRequest);
Here we are trying to inform JPA that this name is foreign key for Employee table.
I'm trying to insert data into database but always get compile time error mentioned below is there anything I'm missing ?
Error i'm getting
error: Entities and POJOs must have a usable public constructor. You can have an empty constructor or a constructor whose parameters match the fields (by name and type). - java.util.List
User Response Model class
#Entity(tableName = "RoomDemo")
data class UserResponse(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "code")
#SerializedName("code")
var code: Int,
#Embedded
#SerializedName("data")
var `data`: Data,
#Embedded
#SerializedName("errors")
var errors: Errors,
#ColumnInfo(name = "message")
#SerializedName("message")
var message: String
)
Data model class
data class Data(
#ColumnInfo(name = "avater")
#SerializedName("avater")
var avater: String,
#Embedded(prefix = "avater")
#SerializedName("user_info")
var userInfo: UserInfo
)
User Info Model class
data class UserInfo(
#ColumnInfo(name = "location")
#SerializedName("location")
var location: String,
#Embedded
#SerializedName("mediafiles")
var mediafiles: List<Mediafile>)
Dao Interface
#Dao
interface CrushDao {
#Insert(onConflict = REPLACE)
fun insert(userResponse: UserResponse)
}
My Database
#Database(entities = [UserResponse::class], version = 2)
abstract class CrushDataBase : RoomDatabase()
{
abstract fun crushDao():CrushDao
companion object{
private var INSTANCE: CrushDataBase? = null
fun getDatabase(context: Context): CrushDataBase? {
if (INSTANCE == null) {
synchronized(CrushDataBase::class) {
INSTANCE = Room.databaseBuilder(
context.getApplicationContext(),
CrushDataBase::class.java, "crushDemo.db"
).build()
}
}
return INSTANCE
}
}
}
#Embedded
#SerializedName("mediafiles")
var mediafiles: List<Mediafile>
AFAIK, you cannot use #Embedded for arbitrary things, which includes lists.
Either change this to use relations or use #TypeConverter and #TypeConverters to convert your List<Mediafile> into some valid column type, such as a String.
I'm trying to write simple world generator using kotlin, springboot and hibernate, and I have many relations in Entities but one of them not working. Program generate the Countries and cities, but in DB I have a null ID for 'capital'
Entities:
#Entity
data class City(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id:Long? = null,
val name: String,
#OneToMany
#JoinColumn(name="CityId")
val flats: List<Flat>)
#Entity
data class Country(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id:Long? = null,
val name: String,
#OneToOne(fetch = FetchType.EAGER)
val capital: City,
#OneToMany
#JoinColumn(name="CountryId")
val cities: List<City>)
Country generator:
#Component
class CountryGenerator #Autowired constructor(val util: Util) {
fun getRandomCountry(): Country = util.getObj(
Country(null,
gen.country().name(),
CityGenerator(util).getRandomCapital(),
getCities())
) as Country
private fun getCities(): List<City> =
IntStream.range(0, rnd.nextInt(MAX_CITIES_NUMBER!!) + MIN_CITIES_NUMBER!!)
.mapToObj { CityGenerator(util).getRandomCity() }
.toList()
}
City generator:
#Component
class CityGenerator #Autowired constructor(val util: Util) {
fun getRandomCity() = util.getObj(
City(null,
getCityName(),
getListOfFlats())
) as City
fun getRandomCapital() = util.getObj(
City(null,
getCapital(),
getListOfFlats())
) as City
private fun getListOfFlats(): List<Flat> =
IntStream.range(0, rnd.nextInt(MAX_FLATS_NUMBER!!) + MIN_FLATS_NUMBER!!)
.mapToObj { FlatGenerator(util).getFlat() }
.toList()
private fun getCapital() = gen.country().capital()
private fun getCityName(): String = gen.address().city()
}
Any ideas what is wrong with it?
EDIT:
Saving to DB:
#Component
class Util(private val personRepository: PersonRepository,
private val flatRepository: FlatRepository,
private val cityRepository: CityRepository,
private val countryRepository: CountryRepository,
private val worldRepository: WorldRepository) {
fun getObj(obj: Any): Any {
return when (obj) {
is Person -> this.personRepository.save(obj)
is Flat -> flatRepository.save(obj)
is City -> cityRepository.save(obj)
is Country -> countryRepository.save(obj)
is World -> worldRepository.save(obj)
else -> throw IllegalArgumentException("Wrong object");
}
}
EDIT2:
Method Util.getObj() returns correct objects:
Confirm that the method Util.getObj is really saving the new city and returning a city with an id.
Also, if this relationship is required, you should use the property optional = false in the OneToOne annotation:
#OneToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name="capital_id", nullable=false)
val capital: City
I have Two Entity User and Account. Both have one to one mapping.
Here is the Entity Classes :
User:
#Entity
#Table(name = 'docutools_users')
public class DocutoolsUser {
#Id
#Type(type = "pg-uuid")
UUID id = UUID.randomUUID();
#OneToOne(mappedBy = "user", cascade = ALL)
Account account;}
Account
#Entity
#Table(name = "accounts")
public class Account {
#Id
#Type(type = "pg-uuid")
private UUID id = UUID.randomUUID();
#Column(nullable = false)
private LocalDate activated = LocalDate.now();
#OneToOne
private DocutoolsUser user;
}
Query
#Query("SELECT u FROM DocutoolsUser u WHERE u.account IS NOT NULL")
Page<DocutoolsUser> findByAccountNotNull()
I am using JPA repositery.
The expression u.account IS NOT NULL always return true even there is no account in user.
Thanks
Exact Query is here
#Query("""SELECT u FROM DocutoolsUser u WHERE u.organisation = :org AND UPPER(CONCAT(u.name.firstName, ' ', u.name.lastName)) LIKE :search AND ((CASE WHEN ( (u.id = u.organisation.owner.id AND u.account IS NULL) OR ( u.account IS NOT NULL AND (u.account.subscription.until IS NULL or u.account.subscription.until > :currentDate)) ) THEN true ELSE false END) IS :isLicensed )""")
Page<DocutoolsUser> findByOrganisationAndLicense(#Param('org') Organisation organisation, #Param('search') String search, #Param('currentDate') LocalDate currentDate, #Param('isLicensed') Boolean isLicensed, Pageable pageable)
you can do this without #Query using JpaRepository and IsNotNull
#Repository
public interface DocutoolsUserRepository extends JpaRepository<DocutoolsUser, Long> {
// where Account not null
public List<DocutoolsUser> findByAccountIsNotNull();
}
for more informations : https://docs.spring.io/spring-data/jpa/docs/1.5.0.RELEASE/reference/html/jpa.repositories.html
for the whole query you can combine many jpaRepository services