DataJpaTest not working with mysql full text search - java

I am trying to test this method:
#Query(value = "select * from table "
+ "where match(name_column) AGAINST(:name in BOOLEAN MODE)", nativeQuery = true)
List<ManagedGroup> findByGroupNameMatches(#Param("name") String groupName);
The test itself :
#Test
public void findByGroupNameMatches_should_return_when_contains_part_of_name(){
ManagedGroup managedGroup = prepearGroup(UUID.randomUUID().toString());
managedGroup.setGroupName("def123");
testEntityManager.persistAndFlush(managedGroup);
// in desperate tries - tried to save it via repositroty as well
groupRepository.save(managedGroup);
//the groups can be found - there is two groups with names 'def123' and 'default name'
List<ManagedGroup> res = groupRepository.findAll();
Pageable pageable = new OffsetLimitPageRequest(0, 10, Sort.by(Sort.Direction.DESC, "id"));
List<ManagedGroup> managedGroupPage = groupRepository.findByGroupNameMatches("+def*");
//managedGroupPage is empty after the method is called
Assertions.assertEquals(defaultManagedGroup, managedGroupPage.get(0));
}
The test fails cause I get empty result from findByGroupNameMatches. I've tried saving and retriviengthe entity via jdbcTemplate - still no result. Other tests work as well, so I guess that it is not the problem.
The method works in implemented controller and is already tested by QA team.
I've also tried to run the test against QA db and it works with the entities, persisted before tests are booted. I am sure that the problem is with persisting it, but I still cannot find the solution. I've tried different combinations of flush and etc.. and still no result
Providing more info to reproduce the test:
static final MySQLContainer DATABASE = new MySQLContainer("mysql:8.0.23");
static {
DATABASE.start();
}
static class Initializer implements
ApplicationContextInitializer<ConfigurableApplicationContext> {
public void initialize(ConfigurableApplicationContext context) {
TestPropertyValues.of(
"spring.datasource.url=" + DATABASE.getJdbcUrl(),
"spring.datasource.username=" + DATABASE.getUsername(),
"spring.datasource.password=" + DATABASE.getPassword()
).applyTo(context.getEnvironment());
}
The test class is annotated with
#DataJpaTest
#ExtendWith(SpringExtension.class)
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#DisabledIfEnvironmentVariable(named = "RUN_INTEGRATION_TEST", matches = "false")
#DirtiesContext
}

Hello shouldnt you have a table name for this native query
#Query(value = "select * from table TABLE_NAME "
+ "where match(name_column) AGAINST(:name in BOOLEAN MODE)", nativeQuery = true)
List<ManagedGroup> findByGroupNameMatches(#Param("name") String groupName);
Also you should flush when Transactional annotation not there
// in desperate tries - tried to save it via repositroty as well
groupRepository.save(managedGroup);
use groupRepository.flush or saveAndFlush

Related

Is it possible to use JpaRepository without entity?

Is it possible to use JpaRepository without entity? In this case, replacing it with a DTO.
as follows the example
#Repository
public interface BffRepository extends JpaRepository<BffDTO, String> {
#Query(nativeQuery = true, value = "select\n"
+ "ent.name as enterprise_name, dep.name as department_name,\n"
+ "sq.name as squad_name, acc.firstname as job_owner_name,\n"
+ "tpt.name as test_template_name, job.name, job.job_blocked, job.job_removed,\n"
+ "job.bot_scm_branch, job.bot_scm_url, job.schedule_startdate,\n"
+ "job.expiration_date, job.timestamp,job.uuid,job.schedule_starttime,\n"
+ "tpt.job_execution_timeout\n"
+ "from portal.jobs job\n"
+ "left join portal.enterprises ent on (ent.uuid = job.enterprise_id)\n"
+ "left join portal.departments dep on (dep.uuid = job.department_id)\n"
+ "left join portal.squads sq on (sq.uuid = job.squad_id)\n"
+ "left join portal.accounts acc on (acc.uuid = job.job_owner)\n"
+ "left join portal.test_plan_templates tpt on (tpt.uuid = job.template_id) where\n"
+ "job.job_owner = ?1 and job.job_removed = false order by timestamp desc;")
List<BffDTO>buscarPorJobOwner(String jobOwner);
Are there alternatives for this case?
NOTE: the DTO is already mapped, but I would not like to create a view to transform this DTO into an Entity.
I already validated this topic, but without major advances
Use JpaRepository interaction style without entity
i'm trying this
Interface -
public interface BffDTOInterface2 {
String uuid();
String enterprise_name();
String department_name();
String squad_name();
String job_owner_name();
String test_template_name();
String name();
Boolean job_blocked();
Boolean job_removed();
String bot_scm_branch();
String bot_scm_url();
String schedule_startdate();
String expiration_date();
String timestamp();
String schedule_starttime();
Integer job_execution_timeout();
#Transient
String status();
}
I'm having this error
Caused by: java.lang.IllegalArgumentException: Not a managed type: interface br.com.cloud.api.domain.dto.BffDTOInterface2
You can use Projections based on interfaces.
e.g
Create your native-query givin it column a alias. select name as fullName, age as age from person.
Create a Interface that represents your DTO with get-methods to every alias of your native query.
interface MyDTO {
String getFullName();
Integer getAge();
}
The return type of your query now can be this MyDTO
#Query(value = "select name as fullName, age as age from person", nativeQuery=true)
List<MyDTO> findMyDTO();
Is it possible to use JpaRepository without entity?
No, it is not, and it would completely defeat the purpose of JPA, by definition.
JPA is the persistence specification that enables ORM - Object Relational Mapping - that is, you map Java objects to database tables' entries/rows, and Java types to database tables, respectively.
DTO (Data Transfer Object) has nothing to do with ORM, and it serves different purpose (I recommend you to read this article for DTO vs. Entity matter) - transferring data through Java objects - and it usually serves the middle layer, for converting persistent objects(#Entitys) into objects to be used in the web layer (DTOs), and vice versa.
If you really want to avoid persistence layer models (#Entitys), you may go for JDBC abstractions (e.g. Spring Data JDBC), native queries, JPQL, HQL, or a bare JDBC API (which I wouldn't recommend).
but you can try this.
What you can do is you can create your own custom repository class. First, you would have some service class that calls repository class. also notice that we have custom models for the result set of SQL queries.
#Service
public class CustomService {
#Autowired
private CustomRepository repository;
public List<CustomResponse> getAllResult(String id) {
List<Object[]> items = repository.getAllResult(id);
List<CustomResponse> customResponseList = new ArrayList();
for (Object[] item: items) {
CustomResponse c = new CustomResponse();
c.setTestValue1(String.valueOf(item[0]));
c.setTestValue2(String.valueOf(item[1]));
customResponseList.add(c);
}
return customResponseList;
}
}
and your repository class will be look like this.
#Repository
public class CustomRepository {
#Autowired
private EntityManager entityManager;
public List<Object[]> getAllResult(String id) {
Query q = (Query) entityManager.createNativeQuery("SELECT\n" +
" users.user_id as user_id,\n" +
" users.email as user_email\n" +
" FROM Users\n" +
" WHERE users.parent_id = :parent_id;");
q.setParameter("parent_id", id);
List<Object[]> results = q.getResultList();
return results;
}
}
Also you might want to have your own model for that. (like entities)
public class CustomResponse {
private String testValue1;
private String testValue2;
public String getTestValue1() {
return testValue1;
}
public void setTestValue1(String testValue1) {
this.testValue1 = testValue1;
}
public String getTestValue2() {
return testValue2;
}
public void setTestValue2(String testValue2) {
this.testValue2 = testValue2;
}
}
This is possible.
Define base entity and have one column. If you dont want this to exist in database, turn off ddl-auto in application.propeties.
spring.jpa.hibernate.ddl-auto=none
#Entity
#Data
public class BaseEntity {
#Id
private Long id;
}
and use any custom query with any other dao extending jpa repository with BaseEntity.
public interface EmployeeDao extends JpaRepository<BaseEntity, Long> {
#Query(value = "select name from employee where employee_number = ?", nativeQuery = true)
Optional<Employee> get(String employeeNumber);
}
public interface Employee{
String getName();
}

Test cases for JPQL and normal Queries

In my project we are caching the data of a table where we use #NamedQuery for fetching the data (Hibernate queries) and we are also using #NamedNativeQueries and my code is like that
#SpringBootTest
public class NonPolygonIntegeration {
#Autowired
private Repository localRepository;
#MockBean
ServicePropertiesRepository servicePropertiesRepository;
#MockBean
CacheService alendarCacheService;
#Autowired
OlygonService olygonService;
#Test
#Order(1)
#Sql(scripts = {"/sqls/mdm.sql", "/sqls/p.sql","/sqls/q.sql",
"/sqls/x.sql",
"/sqls/y.sql",
"/sqls/z.sql"
}, config = #SqlConfig(encoding = "utf-8"))
void testGetAllActiveCurrencies_forDataPresentInDb() {
}
}
"""
I have data in my local database for all the corresponding tables in use.
query which will get in use are
#Query("Select * FROM Properties WHERE id.Code =:serviceCode AND (endDate IS NULL OR endDate >= CURRENT_DATE)")
and other is
select * from empoyee_table where employee_id = :id
How to handle such scenario?

Expose endpoint with native query without creating entity

I want to run some native queries and expose the results through endpoints, but I want to do this without having to create all the entities. I just want the data obtained from the database to be exposed as it comes.
I found some suggestions at: Create spring repository without entity
However, I was not able to make them work. I'm very new to Spring.
I tried Maciej Kowalski's solution like this:
Interface:
public interface CustomNativeRepository {
Object runNativeQuery();
}
Implementation:
#Repository
public class CustomNativeRepositoryImpl implements CustomNativeRepository {
#Autowired
private EntityManager entityManager;
#Override
public Object runNativeQuery() {
return entityManager.createNativeQuery(
"""
SELECT 1 as col1, 2 as col2, 3 as col3
UNION ALL SELECT 4, 5, 6
UNION ALL SELECT 7, 8, 9
"""
)
.getResultList();
}
}
However, no endpoints were exposed, as happens when you extend CrudRepository. Should I have done something else with CustomNativeRepositoryImpl? I don't know how to proceed.
I also tried Gagarwa's solution:
RootEntity:
#Entity
public class RootEntity {
#Id
private Integer id;
}
RootEntityRepository:
#Repository
public interface RootEntityRepository extends JpaRepository<RootEntity, Integer> {
#Query(value = """
SELECT 1 as col1, 2 as col2, 3 as col3
UNION ALL SELECT 4, 5, 6
UNION ALL SELECT 7, 8, 9""",
nativeQuery = true)
public Collection<Object> findFromCustomQuery();
}
The endpoint http://localhost:8080/rootEntities was exposed, but when I accessed it, I got the exception: "Relation root_entity does not exist". So, I created the table in the database:
create table root_entity(
id SERIAL PRIMARY KEY
)
After that, the endpoint worked, and returned an empty array (the table root_entity is empty in the database).
I tried to access the endpoint: http://localhost:8080/rootEntities/search/findFromCustomQuery, but I got an exception (Couldn't find PersistentEntity for type class).
Again, I was not able to make it work.
After trying a lot, I made some progress doing the following:
#RestController
public class CustomQueryController {
#Autowired
private EntityManager entityManager;
#GetMapping("/myEndpoint")
#ResponseBody
public Object runNativeQuery() {
return ResponseEntity
.ok()
.body(
entityManager.createNativeQuery(
"""
SELECT 1 as col1, 2 as col2, 3 as col3
UNION ALL SELECT 4, 5, 6
UNION ALL SELECT 7, 8, 9
"""
).getResultList()
);
}
}
With the code above, I can access http://localhost:8080/myEndpoint and see the result of the query.
However, the endpoint didn't appear in the endpoints listing that is showed in http://localhost:8080/. I had to type it manually in the browser. I would like the endpoint to be exposed in order to see it in Swagger.
Also, I have a feeling that there must be a better way to do this. And I want to learn.
I would like help to:
Get a solution that works and exposes the endpoint.
Understand what I did wrong and how to implement Kowalski's and Gagarwa's solutions.
Being able to expose the endpoint for the last solution (CustomQueryController).
Thanks in advance!
try changing your CustomQueryController to implement RepresentationModelProcessor
public class CustomQueryController implements RepresentationModelProcessor<RepresentationModel<RepositoryLinksResource>> {
and implementing the process method with:
#Override
public RepresentationModel<RepositoryLinksResource> process(RepresentationModel<RepositoryLinksResource> model) {
if (model instanceof RepositoryLinksResource) {
model.add(Link.of( "http://localhost:8080" + "/myEndpoint", "myEndpoint"));
}
return model;
}
https://docs.spring.io/spring-data/rest/docs/current/reference/html/#customizing-sdr.customizing-json-output.representation-model-processor
I tried the first example that you have put here and it worked for me. But there is a bit of change. I have used PersistenceContext.
To return a Link as response I have used Link of WebMvcLinkBuilder.
Solution
In the below example I have used two tables Employee and Address in PostgresSQL . Both have area_code in common.
Interface
public interface CustomNativeRepository {
List<Object> runNativeQuery(Integer name);
}
Repository
#Repository
public class CustomNativeRepositoryImpl implements CustomNativeRepository {
Logger logger = LoggerFactory.getLogger(this.getClass());
#PersistenceContext
private EntityManager entityManager;
#Override
public List<Object> runNativeQuery(Integer areaCode) {
Query query = entityManager.createNativeQuery(
"Select e.first_name as name from employees e where e.area_code = ? "
+ "union all " +
"Select a.address as address from address a where a.area_code = ?");
query.setParameter(1, areaCode);
query.setParameter(2, areaCode);
List<Object> response = query.getResultList();
logger.info("Response from database: {}", response);
return response;
}
}
RestEndpoint Layer
#GetMapping(path ="/employee/{areaCode}")
public ResponseEntity<?> getEmployeeByCode(#PathVariable(value = "areaCode") Integer areaCode) throws NoSuchMethodException {
List<Object> response = customCustomerRepository.runNativeQuery(areaCode);
Link link = WebMvcLinkBuilder.linkTo(WebMvcLinkBuilder.methodOn(EmployeeController.class).getEmployeeByCode(areaCode)).withSelfRel();
return ResponseEntity.ok().body(CollectionModel.of(response, link));
}
Few examples which may help. link1   link2
Note: I have not created any Entity classes in my code base.

What caused the PersistenceException with the message "detached entity passed to perist"

I'm using:
Quarkus with JPA (javax)
Postgres 11 database
I have:
An Entity
#Entity
#Table(name = "MyEntityTable")
#NamedQuery(name = MyEntity.DOES_EXIST, query = "SELECT x FROM MyEntity x WHERE x.type = :type")
public class MyEntity {
public static final String DOES_EXIST = "MyEntity.DoesExists";
#Id
#SequenceGenerator(name = "myEntitySequence", allocationSize = 1)
#GeneratedValue(generator = myEntitySequence)
private long id;
#Column(name = type)
private String type;
}
A repository
#ApplicationScoped
#Transactional(Transactional.TxType.Supports)
public class MyEntityReporitory {
#Inject
EntityManager entityManager;
#Transactional(Transactional.TxType.Required)
public void persist(final MyEntity entity) {
entityManager.persist(entiy);
}
public boolean doesExist(final String type) {
final TypedQuery<MyEntity> query = entityManager
.createNamedQuery(MyEntity.DOES_EXIST, MyEntity.class)
.setParameter("type", type);
return query.getResultList().size() > 0;
}
}
A test with two variations
Variation 1
#QuarkusTest
#QuarkusTestResource(DatabaseResource.class) // used to set up a docker container with postgres db
public class MyEntityRepositoryTest {
private static final MyEntity ENTITY = entity();
#Inject
MyEntityRepository subject;
#Test
public void testDoesExist() {
subject.persist(ENTITY);
final boolean actual = subject.doesExist("type");
assertTrue(actual);
}
#Test
public void testDoesExist_notMatching() {
subject.persist(ENTITY);
final boolean actual = subject.doesExist("another_type");
assertFalse(actual);
}
private static MyEntity entity() {
final MyEntity result = new MyEntity();
result.setType("type")
return result;
}
}
When I execute this test class (both tests) I'm getting the following Exception on the second time the persist method is called:
javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist com.mypackage.MyEntity
...
Variation 2
I removed the constant ENTITY from the test class, instead I'm calling now the entity() method inside the tests, like:
...
subject.persist(entity());
...
at both places. Now the Exeption is gone and everything is fine.
Question
Can someone explain to me, why this is the case (why variante 2 is working and variante 1 not)?
https://vladmihalcea.com/jpa-persist-and-merge/
The persist operation must be used only for new entities. From JPA perspective, an entity is new when it has never been associated with a database row, meaning that there is no table record in the database to match the entity in question.
testDoesExist executed, ENTITY saved to database and ENTITY.id set to 1
testDoesExist_notMatching executed and persist called on ENTITY shows the error beacuse it exists in the database, it has an id assigned
The simplest fix is to call entity() twice, as in you variation 2.
But don't forget that the records will exist after a test is run, and might affect your other test cases. You might want to consider cleaning up the data in an #After method or if you intend to use this entity in multiple test cases then put the perist code into a #BeforeClass method.

AutoIncrement ID not reseting for rest integration tests

I have a simple rest API using spring boot 2 and Java 11, and I have some integration tests. Some of the tests insert into the DB and assert that the data is read from the API, and some create objects via the API and assert they can be read back from the DB or the API.
The latter tests, which create objects via the API, seem to not be reseting the auto-increment value in the test h2 database. The symptom is that the tests pass independently, but not when run as a group - the IDs are the sum of all inserts so far.
I have an #Before method that drops and recreates every table, and an #After method that drops every table, and I've verified that they are running.
There are lots of questions about this but none of the suggested answers make any difference. I have tried:
Decorating the test class with #DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
Decorating the test methods with #Transactional
Running ALTER TABLE person ALTER COLUMN id RESTART WITH 1; repeatedly, including after the table creation and again right before each object creation
My code looks like this:
IntegrationTest.java
#EnableConfigurationProperties
#RunWith(SpringJUnit4ClassRunner.class)
#PropertySource(value = "classpath:application.properties")
#SpringBootTest(classes = Application.class, webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public abstract class IntegrationTest {
#Autowired
private DataSource dataSource;
#Autowired
JdbcTemplate jdbcTemplate;
RestTemplate restTemplate = new RestTemplate();
#Before
public void SetUp() throws SQLException {
ScriptUtils.executeSqlScript(dataSource.getConnection(), new FileSystemResource("src/test/resources/initialization.sql"));
}
#After
public void CleanUp() throws SQLException {
ScriptUtils.executeSqlScript(dataSource.getConnection(), new FileSystemResource("src/test/resources/cleanup.sql"));
}
}
PersonTest.java
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class PersonTest extends IntegrationTest {
#Test
#Transactional
public void TestCreatePerson() {
String url = "http://localhost:8080/people";
Person p = new Person();
p.setFirstName("Joe");
p.setLastName("Smith");
Person r = restTemplate.postForObject(url, p, Person.class);
Assert.assertEquals(1, (long) r.getId());
}
#Test
#Transactional
public void TestUpdatePerson() {
jdbcTemplate.execute("alter table person alter column id restart with 1");
String url = "http://localhost:8080/people";
Person p = new Person();
p.setFirstName("Joe");
p.setLastName("Smith");
Person r = restTemplate.postForObject(url, p, Person.class);
Assert.assertEquals(1, (long) r.getId()); // FAILS HERE BECAUSE ID == 2
r.setLastName("Jones");
restTemplate.put(url, r);
url = "http://localhost:8080/people/1";
Person u = restTemplate.getForObject(url, Person.class);
Assert.assertEquals("Jones", u.getLastName());
}
initialization.sql
DROP TABLE IF EXISTS person;
CREATE TABLE IF NOT EXISTS person (
id int(11) NOT NULL AUTO_INCREMENT,
first_name varchar(128) NOT NULL,
last_name varchar(128) NOT NULL
);
-- for good measure
ALTER TABLE person ALTER COLUMN id RESTART WITH 1;
cleanup.sql
DROP TABLE IF EXISTS person;
Any ideas what I am doing wrong?

Categories

Resources