JPA manyToMany not persisting in assocation table - spring

I'm trying the following unit test:
#Test
#Transactional
public void thatFolderLocationAssociationTableIsWorking() {
Location l1 = new DomesticLocation();
l1.setDeptName("Test name 1");
Location l2 = new DomesticLocation();
l2.setDeptName("Test name 2");
KMLFolder k1 = new KMLFolder();
k1.setName("Test name 1");
KMLFolder k2 = new KMLFolder();
k1.setName("Test name 2");
List<Location> locations = new ArrayList<Location>();
locations.add(l1);
locations.add(l2);
k1.setLocations(locations);
kmlFolderServiceImpl.save(k1);
assertEquals("Test name 1", kmlFolderServiceImpl.find(1L).getLocations().get(0).getDeptName());
assertEquals("Test name 2", kmlFolderServiceImpl.find(1L).getLocations().get(1).getDeptName());
//The following line gets the NPE
assertEquals("Test name 1", locationServiceImpl.find(1L).getKmlFolderList().get(0).getName());
}
I'm getting NPEs on the laster assertions where I'm trying to retrieve KMLFolder.getName() from the Locations The other assertions are working, where I get the Location name from the KMLFolder.
Here are my JPA definitions:
#ManyToMany(mappedBy="kmlFolderList", cascade=CascadeType.ALL)
private List<Location> locations = new ArrayList<Location>();
#ManyToMany
#JoinTable(name="LOCATION_KMLFOLDER",
joinColumns={#JoinColumn(name="KMLFOLDER_ID", referencedColumnName="ID")},
inverseJoinColumns={#JoinColumn(name="LOCATION_ID", referencedColumnName="ID")}
)
The appropriate table is being created when I run the test. Here's the console output:
Hibernate:
create table project.LOCATION_KMLFOLDER (
KMLFOLDER_ID bigint not null,
LOCATION_ID bigint not null
) ENGINE=InnoDB
...
Hibernate:
alter table project.LOCATION_KMLFOLDER
add index FK_lqllrwb2t5cn0cbxxx3ms26ku (LOCATION_ID),
add constraint FK_lqllrwb2t5cn0cbxxx3ms26ku
foreign key (LOCATION_ID)
references project.KMLFolder (id)
Hibernate:
alter table .LOCATION_KMLFOLDER
add index FK_ckj00nos13yojmcyvtefgk9pl (KMLFOLDER_ID),
add constraint FK_ckj00nos13yojmcyvtefgk9pl
foreign key (KMLFOLDER_ID)
references project.Locations (id)
The console does not show inserts in to the LOCATION_KNLFOLDER table as I expect. Any thoughts on why this may be happening?

You're initializing the inverse side of the association, that Hibernate ignores, instead of (or in addition to) initializing the owner side of the association, that Hibernate doesn't ignore.
The owner side is the side without the mappedBy attribute.

Related

SpringBoot+Liquibase+TestContainer db - not able to populate db during integration test

I am trying to use TestContainers for my integration tests. What I am trying to do is use sql script to populate with some data, and then also add new data using tests. Below is the test setup:
Integration test where I am trying to get the data inserted through sql scripts:
#SpringBootTest
#AutoConfigureMockMvc
#ExtendWith(SpringExtension.class)
#AutoConfigureTestDatabase(connection = EmbeddedDatabaseConnection.H2)
#TestPropertySource(value = {
"classpath:application-test.properties"
})
class EmployeeDatabaseApplicationTests
#Test
void getEmployeeByEmployeeId() throws Exception {
List<UUID> employeeIds = List.of();
this.mockMvc.perform(get("/admin/employees")
.accept(EmployeeProfileUtil.MEDIA_TYPE_JSON_UTF8)
.contentType(EmployeeProfileUtil.MEDIA_TYPE_JSON_UTF8)
.header("Employee-id", "cc95ccff-8169-4559-9806-1ca4a1db3a19"))
.andExpect(status().is2xxSuccessful());
}
application-test.properties:
spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver
spring.datasource.url=jdbc:tc:postgresql://employee
spring.jpa.hibernate.ddl-auto = create
spring.liquibase.contexts=test
spring.liquibase.change-log=classpath:/db-test.changelog/db.changelog-test-master.yaml
db.changelog-test-master.yaml
databaseChangeLog:
- includeAll:
path: classpath*:db-test.changelog/changes/
changes folder has 2 sql files, one creates the database schema and the other populates some of the created schema. ex:
CREATE TABLE IF NOT EXISTS employee (
id int8 generated by default as identity,
date_of_birth varchar(255),
deleted boolean not null,
employee_id uuid,
gender varchar(255),
phone varchar(255),
name_id int8,
primary key (id)
);
INSERT INTO employee (id, date_of_birth, deleted, employee_id, gender, phone, name_id) values (1, '2010-02-02', false, 'cc95ccff-8169-4559-9806-1ca4a1db3a19',
'female', '5561132977', 1);
The change log is getting picked as I can see in logs:
liquibase.changelog : Custom SQL executed
liquibase.changelog : ChangeSet db-test.changelog/changes/employee-create-tables-20220810.sql::raw::includeAll ran successfully in 22ms
liquibase.changelog : ChangeSet db-test.changelog/changes/employee-create-tables-20221010.sql::raw::includeAll ran successfully in 6ms
To summarize:
I want to query the database using employe_id- cc95ccff-8169-4559-9806-1ca4a1db3a19, as I(think)am inserting this to db, which currently doesn't return the data.
Thanks for the help!
Liquibase is already being used to create perform the scripts. For that reason the following property should be spring.jpa.hibernate.ddl-auto=none at application-tests.properties in order to override the one in application.properties.
I already saw you fixed in the code provided but #AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)

SpringBootTest - H2 - NULL not allowed for column "ID"

I'm trying to insert some data on H2 in memory database to make some tests and simulate my real Oracle11g but I'm facing an error NULL not allowed for column "ID". I've tried many solutions like putting Id and default on insert query, changing to Mode=LEGACY on jdb-url, but it didn't work.
data.sql
INSERT INTO CSM_SECURITY.csm_person(EMAIL, PERSON_NAME, SYS_ADMIN, DEFAULT_LANGUAGE, WSO2_ID, SYS_ADM_COUNTRY_ID, SYS_ADMIN_COUNTRY)
VALUES ('admin#wso2.com','admin','Y','es-ar',NULL,NULL,NULL);
Person Entity
#Entity
#Table(name = "csm_person", uniqueConstraints = #UniqueConstraint(columnNames = "email"))
public class Person implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "PERSON_SEQ")
#SequenceGenerator(sequenceName = "CSM_PERSON_ID_SEQ", allocationSize = 1, name = "PERSON_SEQ")
private Long id;
#Column(name = "email")
private String email;
#Column(name = "person_name")
private String personName;
...
src/test/resources/application.properties
# SQL properties
spring.jpa.defer-datasource-initialization=true
spring.jpa.database-platform = org.hibernate.dialect.H2Dialect
spring.jpa.show-sql = true
spring.jpa.properties.hibernate.format_sql = true
spring.jpa.properties.hibernate.default_schema = CSM_SECURITY
spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.H2Dialect
spring.jpa.properties.hibernate.event.merge.entity_copy_observer = allow
spring.jpa.open-in-view = false
spring.datasource.driver-class-name = org.h2.Driver
spring.sql.init.platform = h2
spring.datasource.name = CSM_SECURITY
spring.datasource.url = jdbc:h2:mem:CSM_SECURITY;MODE=Oracle;DB_CLOSE_DELAY=-1;INIT=CREATE SCHEMA IF NOT EXISTS CSM_SECURITY
spring.sql.init.mode = embedded
spring.datasource.username = sa
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
Error
Caused by: org.springframework.jdbc.datasource.init.ScriptStatementFailedException:
Failed to execute SQL script statement #1 of
URL [file:/home/ainacio/Development/csm-security/target/test-classes/data.sql]:
INSERT INTO CSM_SECURITY.csm_person(EMAIL, PERSON_NAME, SYS_ADMIN, DEFAULT_LANGUAGE, WSO2_ID, SYS_ADM_COUNTRY_ID, SYS_ADMIN_COUNTRY)
VALUES ('admin#wso2.com','admin','Y','es-ar',NULL,NULL,NULL);
nested exception is org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: NULL not allowed for column "ID"; SQL statement:
For such insertions you need to use identity columns (GenerationType.IDENTITY), but they aren't available in Oracle 11g, only 12c and newer versions support them.
You need to provide a value for ID column but this value must not conflict with values produced by a sequence, for example, you can change start value of a sequence generator to a 100, and use smaller values in your initialization scripts.
Alternatively you can fetch value of a sequence by itself, but Oracle 11g doesn't support sequence value expressions inside insert values or inside subqueries. So if you need to execute the same SQL in H2 and that historic version of Oracle, you need to execute something like that:
INSERT INTO CSM_SECURITY.CSM_PERSON
(ID, EMAIL, PERSON_NAME, SYS_ADMIN, DEFAULT_LANGUAGE,
WSO2_ID, SYS_ADM_COUNTRY_ID, SYS_ADMIN_COUNTRY)
SELECT CSM_SECURITY.CSM_PERSON_ID_SEQ.NEXTVAL,
'admin#wso2.com', 'admin', 'Y', 'es-ar', NULL, NULL, NULL FROM DUAL;
Make sure you're using Oracle compatibility mode of H2, because new versions of H2 don't accept NEXTVAL in Regular mode.
If you use this initilalization script only with H2, that trick with insert from query isn't required:
INSERT INTO CSM_SECURITY.CSM_PERSON
(ID, EMAIL, PERSON_NAME, SYS_ADMIN, DEFAULT_LANGUAGE,
WSO2_ID, SYS_ADM_COUNTRY_ID, SYS_ADMIN_COUNTRY)
VALUES (NEXT VALUE FOR CSM_SECURITY.CSM_PERSON_ID_SEQ,
'admin#wso2.com', 'admin', 'Y', 'es-ar', NULL, NULL, NULL);

How to get the Primary Key when doing Batch Update in Spring Jdbc NamedParameterJdbcTemplate?

I am working on Spring Batch and Spring Jdbc where I'm using NamedParameterJdbcTemplate's batchUpdate to insert the record into DB and once the records are inserted I want to get the Primary Keys of all inserted records..
Is there any way to get the generated Primary Keys?
List<Person> persons = Arrays.asList(
Person.create("Dana", "Whitley", "464 Gorsuch Drive"),
Person.create("Robin", "Cash", "64 Zella Park")
);
String sql = "insert into Person (first_Name, Last_Name, Address) " +
"values (:firstName, :lastName, :address)";
List<Map<String, Object>> batchValues = new ArrayList<>(persons.size());
for (Person person : persons) {
batchValues.add(
new MapSqlParameterSource("firstName", person.getFirstName())
.addValue("lastName", person.getLastName())
.addValue("address", person.getAddress())
.getValues());
}
int[] updateCounts = namedParamJdbcTemplate.batchUpdate(sql,
batchValues.toArray(new Map[persons.size()]));
I answered a similar question here
The trick is to use NamedParamJdbcTemplate#query over NamedParamJdbcTemplate#batchUpdate and add returning <generated_column> to your insert sql

How to use arrays with Spring Data JDBC

I am trying to use Spring Data JDBC for my PostgreSQL database. I defined the following beans
#Data
class Report {
#Id
private Long id;
private String name;
private Set<Dimension> dimensions;
}
#Data
class Dimension {
private String name;
private Long[] filterIds;
}
and the corresponding DDL
CREATE TABLE report (
id bigserial PRIMARY KEY,
name text NOT NULL
);
CREATE TABLE dimension (
id bigserial PRIMARY KEY ,
report bigint,
name text,
filter_ids bigint[],
FOREIGN KEY (report) REFERENCES report(id) ON DELETE CASCADE ON UPDATE CASCADE
);
Then I tried to insert a report
final Dimension dimension = new Dimension();
dimension.setName("xyz");
dimension.setFilterIds(new Long[]{ 1L, 2L, 3L });
final Report report = new Report();
report.setName("xyz");
report.setDimensions(Collections.singleton(dimension));
repository.save(report);
where repository is simply a CrudRepository<Report, Long>.
This gave me the following error
org.postgresql.util.PSQLException: ERROR: column "filter_ids" is of type bigint[] but expression is of type bigint
Hinweis: You will need to rewrite or cast the expression.
Position: 116
Can I somehow tell Spring Data JDBC how to map the array types?
With the release of Spring Data JDBC 1.1.0, this became possible. See the documentation here:
The properties of the following types are currently supported:
All primitive types and their boxed types (int, float, Integer, Float, and so on)
Enums get mapped to their name.
String
java.util.Date, java.time.LocalDate, java.time.LocalDateTime, and java.time.LocalTime
Arrays and Collections of the types mentioned above can be mapped to columns of array type if your database supports that.
...
As P44T answered this should work from version of 1.1 of Spring Data JDBC onwards just as you used it.
Original answer
It is currently not possible. There are issues for this. A starting point is this one: https://jira.spring.io/browse/DATAJDBC-259

How to retrive a generated primary key when a new row is inserted in the oracle database using Spring JDBC?

Below is the code I am using to save a record in the database and then get the generated primary key.
public void save(User user) {
// TODO Auto-generated method stub
Object[] args = { user.getFirstname(), user.getLastname(),
user.getEmail() };
int[] types = { Types.VARCHAR, Types.VARCHAR, Types.VARCHAR };
SqlUpdate su = new SqlUpdate();
su.setJdbcTemplate(getJdbcTemplate());
su.setSql(QUERY_SAVE);
setSqlTypes(su, types);
su.setReturnGeneratedKeys(true);
su.compile();
KeyHolder keyHolder = new GeneratedKeyHolder();
su.update(args, keyHolder);
int id = keyHolder.getKey().intValue();
if (su.isReturnGeneratedKeys()) {
user.setId(id);
} else {
throw new RuntimeException("No key generated for insert statement");
}
}
But its not working, It gives me following error.
The generated key is not of a supported numeric type. Unable to cast [oracle.sql.ROWID] to [java.lang.Number]
The row is being inserted in the database properly. As well I could get the generataed primary key when using MS SQL database but the same code is not working with the ORACLE 11G.
Please help.
As in the comment, oracle rowid's are alpha numerical so can't be cast to an int.
Besides that, you should not use the generated rowid anywhere in your code. This is not the primary key that you defined on the table.
MS SQL has the option to declare a column as a primary key which auto-increments. This is a functionality that does not work in oracle.
What I always do (regardless if the db supports auto-increment) is the following:
select sequenceName.nextval from dual
The value returned by the previous statement is used as the primary key for the insert statement.
insert into something (pk, ...) values (:pk,:.....)
That way we always have the pk after the insert.

Resources