As part of learning r2DBC i have come across an issue facing with Enum conversion. I am using PostgreSQL here.
When reading data for Film where rating is PG-13 and NC-17( anything with dash ) i am facing issues.
Below is my schema for table
create table film
(
film_id integer default nextval('film_film_id_seq'::regclass) not null
constraint film_pkey
primary key,
title varchar(255) not null,
description text,
release_year year,
language_id smallint not null
constraint film_language_id_fkey
references language
on update cascade on delete restrict,
rental_duration smallint default 3 not null,
rental_rate numeric(4, 2) default 4.99 not null,
length smallint,
replacement_cost numeric(5, 2) default 19.99 not null,
rating mpaa_rating default 'G'::mpaa_rating,
last_update timestamp default now() not null,
special_features text[]
);
And the mpaa_rating is defined as
create type mpaa_rating as enum ('G', 'PG', 'PG-13', 'R', 'NC-17');
This is my code which registers the converters in my Configuration
#Configuration
#EnableTransactionManagement
#EnableR2dbcRepositories
#EnableR2dbcAuditing
public class DVDRentalDBConfiguration extends AbstractR2dbcConfiguration {
#Bean
public ConnectionFactory connectionFactory() {
System.out.println("Initializing postgreSQL connection factory");
return new PostgresqlConnectionFactory(
PostgresqlConnectionConfiguration.builder()
.host("localhost")
.database("dvdrental")
.username("postgres")
.password("postgres")
.codecRegistrar(EnumCodec.builder().withEnum("mpaa_rating", Rating.class).build())
.build()
);
}
#Override
protected List<Object> getCustomConverters() {
return Collections.singletonList(new RatingWritingConverter());
}
#Bean
ReactiveTransactionManager transactionManager(ConnectionFactory connectionFactory) {
System.out.println("Initializing postgreSQL connection factory");
return new R2dbcTransactionManager(connectionFactory);
}
}
My code for retrieving is pretty simple
private Mono<FilmModel> getFilmById(Long id) {
return filmRepository.findById(id).switchIfEmpty(Mono.error(DataFormatException::new));
}
Adding the exception which is thrown https://gist.github.com/harryalto/bd51bbcdd081868c5064c808d08205e4
I tried researching stack overflow but couldn't figure out the issue. Any help is greatly appreciated.
If you are using Spring Boot/Spring Data R2dbc to map table to POJO, you can skip the enum definition in Postgres, by default Spring Data R2dbc will handle the enum as varchar/char in db side, and use Enum in java POJO, check my example, and schema sql script and mapped entity class. Spring Boot registered mapping converter to convert them automatically.
If you would like to handle the Enum type yourself, check this example.
Related
I am writing a junit test using Spring Boot JPA.
My entity has an attribute of type UUID (BaseEntity defines a Long id which is a sequence in database). My mapping is in a orm.xml file:
public class User extends BaseEntity {
private String username;
private UUID uuid;
...
}
I have defined a UserRepoImpl class that search for a User using a given UUID (jpaRepo being an interface extending JpaRepository<User,Long>:
public Optional<User> getUserByUuid(UUID aUuid) {
return jpaRepo.findByUuid(aUuid);
}
I have written a junit to test this method against a H2 database and I use a sql file to insert data before the test :
#ActiveProfiles("tu")
#ExtendWith(SpringExtension.class)
#DataJpaTest(includeFilters = #ComponentScan.Filter(type = FilterType.ANNOTATION, classes = Repository.class))
#Sql("/sql/infra/repo/user-repository.sql")
public class UserRepositoryImplTest {
#Autowired
private UserRepositoryImpl cut;
#Test
void should_ReturnUser_WhenUUIDExist() {
UUID uuid = UUID.fromString("9fc1cd41-9d28-463f-94b9-542836572802");
Optional<User> user = cut.getUserByUuid(uuid);
Assertions.assertTrue(user.isPresent());
Assertions.assertEquals(1L, user.get().getId());
Assertions.assertEquals(uuid, user.get().getUuid());
}
}
The SQL file inserts a user with a UUID that I have converted :
INSERT INTO USERS (id, USERNAME, UUID)
VALUES (3, 'user3', X'9FC1CD419D28463F94B9542836572802');
The test fails because getUserByUuid() doesn't return any user.
What I don't understand is why the UUID column is generated in H2 with a binary type when there is a UUID type in H2 :
Hibernate: create table users (id bigint not null, username varchar(255), uuid binary(255), account_id bigint, primary key (id))
I tried to use varchar for storing the UUID and that works but I don't want to use varchar to store my UUID.
I use https://www.piiatomi.org/uuid_converter.html to make conversion between UUID and hex.
Any idea ?
Thank you!
binary(255) is a fixed-length binary string (byte array in Java) with exactly 255 bytes. This is a wrong data type for UUID values, but some versions of Hibernate ORM incorrectly choose it for UUID properties.
You can override this default with some correct type:
#Column(columnDefinition="UUID")
private UUID uuid;
Before:
public class MyEntity
{
public string Id { get; set; }
//...
}
Config:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
//...
modelBuilder.Entity<MyEntity>()
.Property(e => e.Id)
.ValueGeneratedOnAdd();
}
This was the previous developer's code which resulted in GUID values for the column. But in C# I had to deal with strings, so I decided to change the model.
After:
public class MyEntity
{
public Guid Id { get; set; }
//...
}
And I removed the ValueGeneratedOnAdd() code from Fluent API config.
I get the column "Id" cannot be cast automatically to type uuid error.
I think the key in this message is the automatically word.
Now my question is that since the values on that column are already GUID/UUID, is there any way to tell Postgres to change the varchar type to uuid and cast the current string value to UUID and put it in the column? I'm guessing there should be a SQL script that can do this without any data loss.
Use USING _columnname::uuid. Here is an illustration.
-- Prepare a test case:
create table delme (x varchar);
insert into delme (x) values
('b575ec3a-2776-11eb-adc1-0242ac120002'),
('4d5c5440-2776-11eb-adc1-0242ac120002'),
('b575f25c-2776-11eb-adc1-0242ac120002');
-- Here is the conversion that you need:
ALTER TABLE delme ALTER COLUMN x TYPE uuid USING x::uuid;
In your particular case:
ALTER TABLE "MyEntity" ALTER COLUMN "Id" TYPE uuid USING "Id"::uuid;
Btw, is your application the sole owner of the database model? If not then changing an existing table is a bad idea.
I tried to use Spring Data R2dbc/Postgres in a sample application.
Spring Boot 2.4.0-M2
R2dbc Postgres (managed by Spring Boot)
Spring Data R2dbc 1.2.0-M2(managed by Spring Boot)
The table scripts.
CREATE SEQUENCE IF NOT EXISTS ORDERS_ID_SEQ;
CREATE TABLE IF NOT EXISTS ORDERS(
ID INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('ORDERS_ID_SEQ') ,
CUST_ID BIGINT NOT NULL,
AMOUNT REAL NOT NULL
);
ALTER SEQUENCE ORDERS_ID_SEQ OWNED BY ORDERS.ID;
The data.sql:
-- INSERT SAMPLE DATA
DELETE FROM ORDERS;
INSERT INTO ORDERS(CUST_ID, AMOUNT) VALUES (1, 100.2);
I use a ResourceDatabasePopulator to populate the data, it works.
But when I was trying to save the data by Repository, failed.
#Table(value = "ORDERS")
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class Order implements Serializable {
#Id
#Column(value = "ID")
private Integer id;
#Column(value = "CUST_ID")
private Long customerId;
// use BigDecimal or Java Money API in the real-world application.
#Column(value = "AMOUNT")
private Double amount;
}
public interface OrderRepository extends R2dbcRepository<Order,Integer> {
}
// in application runner.
orders .save(Order.builder().customerId(c.getId()).amount(201.0).build())
It threw an exception like this:
reactor.core.Exceptions$ErrorCallbackNotImplemented: java.lang.UnsupportedOperationException: Binding parameters is not supported for the statement 'INSERT INTO ORDERS (CUST_ID, AMOUNT) VALUES (?, ?)'
Caused by: java.lang.UnsupportedOperationException: Binding parameters is not supported for the statement 'INSERT INTO ORDERS (CUST_ID, AMOUNT) VALUES (?, ?)'
at io.r2dbc.postgresql.SimpleQueryPostgresqlStatement.bind(SimpleQueryPostgresqlStatement.java:78) ~[r2dbc-postgresql-0.8.4.RELEASE.jar:0.8.4.RELEASE]
at io.r2dbc.postgresql.SimpleQueryPostgresqlStatement.bind(SimpleQueryPostgresqlStatement.java:44) ~[r2dbc-postgresql-0.8.4.RELEASE.jar:0.8.4.RELEASE]
The complete codes is here.
Updated: Give up extending from AbstractR2dbcConfiguration, and get resovled when following the official guide.
I have 2 related tables and i am wondering if it is possible to include other columns (like firstname and surname) from table no1 when i am calling the function ListAll() in table no2? I am using JPA with Session Beans (AbstractFacade) and JSF Pages. Thanks in advance :-)
Table No1
CREATE TABLE cases (
caseid INT AUTO_INCREMENT PRIMARY KEY,
category VARCHAR(32),
descript VARCHAR(512),
isDone TINYINT,
userid INT,
INDEX user_ind(userid),
FOREIGN KEY (userid) REFERENCES USERS(userid)
);
Table 2
CREATE TABLE users (
userid INT NOT NULL AUTO_INCREMENT,
firstname VARCHAR(64) NOT NULL,
surname VARCHAR(64) NOT NULL,
telephone VARCHAR(12),
email VARCHAR(64),
PRIMARY KEY (userid)
);
Entity Controller
#Named(value = "caseController")
#SessionScoped
public class CaseController implements Serializable {
#EJB
CasesFacade casesFacade;
#Inject
CasesBean casesBean;
public List<Cases> getAll() {
return casesFacade.findAll();
}
If i correct understand maybe the good idea will be unpack chosen columns/fields to custom DTO on level repository or in code.
I want to integrate a JDBC based application into an Java EE application running in GlassFish which uses EclipseLink JPA 2.5 with MariaDB (a MySQL clone).
The JDBC based application imports data from flat files into some database tables and requires to update the next serial value for the primary keys by executing:
UPDATE dbtable SET nextid = 4711 WHERE name = 'table4Import';
using a standard JDBC connection. This statement throws an SQL timeout exception because the row is locked. It seems to be locked by EclipseLink which uses the same table for its table generator. Here is (parts of) the table containing the serial values:
CREATE TABLE dbtable (
dbtid bigint not null primary key,
dbtname varchar ( 50 ) not null,
dbtnextid bigint );
This is the code for one of the JPA entities:
#Entity
#Access(AccessType.PROPERTY)
#Table( name = "table4Import" )
public class Table4ImportClass {
private Long id = null;
#NotNull
#Id
#TableGenerator( name="table4Import", allocationSize=1,
table="dbtable", pkColumnName="dbtname",
pkColumnValue="table4Import", valueColumnName="nextid" )
#GeneratedValue( generator="table4Import", strategy=GenerationType.TABLE )
public Long getId() { return this.id; }
...
Addendum: It is not even possible to insert a new row into that table. EclipseLink seems to lock the complete table. It is still possible to modify the table (insert & update) from another program. The lock scope seems to be the JVM in which EclipseLink is running.
Addendum 2: Put this as a bug into EclipseLink bug tracker 455756