How to migrate room database? - java

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

Jpa - How to "findBy" two columns while both of them have the same name

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.

Pageable + #Query + JOIN (fetch?) in Spring Data don't work

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.

Room Android Entities and POJOs must have a usable public constructor

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.

Hibernate #OneToOne does not map id

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

IS NOT NULL always return true JPA query

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

Categories