Spring Boot + Elastic Search - elasticsearch

I am trying to setup an application with Spring Boot and Elastic Search. This application already use Spring Data JPA repositories to persist my entities. The problem that I have when I try to run the application with the ElasticSearch configuration enabled is that I am getting an exception when the repositories are scanned.
I am getting the following exception:
Caused by: java.lang.IllegalArgumentException: Unable to obtain mapping metadata for int!
My repository is defined in the following way:
#Repository
public interface AdminUserRepository extends PagingAndSortingRepository<AdminUser, Long> {
/**
* Returns an AdminUser that match the email specified by parameter.
* #param email AdminUser email.
* #return An AdminUser instance.
*/
AdminUser findByEmail(final String email);
/**
* Returns an AdminUser that match the email and business name specified by parameter.
* #param email AdminUser email.
* #param businessName Business Name.
* #return number of matching instances.
*/
int countByEmailAndBusinessName(final String email, final String businessName);
}
Seems that the exception occurs due to the signature of the count query which returns an int. Even though this repository works fine with JPA, it throws an exception enabling elastic search.
I want to know if there is any restrictions with the return type in a repository or if I am missing something in my configuration.
My Java config class:
#Configuration
#EnableElasticsearchRepositories
public class ElasticSearchConfig {
}
Thanks!

It looks like Spring Data Elasticsearch is finding a repository intended for use with Spring Data JPA. When you're using multiple Spring Data modules in the same application, you should place the repositories in separate packages and then reference this package on the #Enable... annotation.
For example:
#Configuration
#EnableElasticsearchRepositories("com.foo.elasticsearch")
#EnableJpaRepositories("com.foo.jpa")
public class MyConfiguration {
}

For counts spring data uses Long not int. Try changing the type of the method that should work.
Here is a reference at the docs: http://docs.spring.io/autorepo/docs/spring-data-elasticsearch/2.0.2.RELEASE/reference/html/#repositories.core-concepts

Related

Filter Results Using Query Parameter

I am trying to use the #Param annotation for a custom query I built using Spring Data JPA's Query Creation such that when I pass the query parameter ?organizationId=2, only Tasks will be returned that have an organization ID that match the given param.
#RepositoryRestResource(collectionResourceRel = "tasks", path = "tasks")
public interface TaskRepository extends JpaRepository<Task, Long> {
List<Task> findByOrganizationId(#Param("organizationId") Long organizationId);
}
The problem is that when I visit the the #RepositoryRestResource path at /tasks?organizationId=2, it seems to be calling the default List<Task> findAll(); method exposed at /tasks, and all Tasks are returned.
How can I make Spring direct the request to my custom method?
All query method resources are exposed under the search resource. (See here for further information.)
http://localhost:8080/tasks/search/ should give you the list of available search endpoints. One should be : http://localhost:8080/tasks/search/findByOrganizationId, to which you can apply your parameterized search.

Spring Boot + MongoDB+create collection

I have a problem. After having created a Spring Boot project with Eclipse and configuring the application.properties file, my collections are not created, whereas after execution, the Eclipse console signals that the connection to MongoDB has been carried out normally. I don't understand what's going on. With MySQL we had the tables created so I expected the creation of the collections, but nothing.
Summary, I don't see my collection (class annoted #Document) in MongoDB after deployment.
New collection won't be created until you insert at least one document. Refer the document Create Collection
You could do this in two ways through Spring. I tested the instructions below in Spring 2.1.7.
With just a #Document class, Spring will not create a collection in Mongo. It will however create a collection if you do the following:
You have a field you want to index in the collection, and you annotate it as such in the Java class. E.g.
#Indexed(unique = true)
private String indexedData;
Create a repository for the collection:
public interface MyClassRepository extends MongoRepository<MyClass, String> {
}
If you don't need/want an index, the second way of doing this would be to add some code that runs at startup, adds a dummy value in the collection and deletes it again.
#Configuration
public class LoadDatabase {
#Bean
CommandLineRunner initDb(MyClassRepository repository) {
// create an instance of your #Document annotated class
MyClass myDocument = new MyClass();
myDocument = repository.insert(myDocument);
repository.delete(myDocument);
}
}
Make sure your document class has a field of the correct type (String by default), annotated with #Id, to map Mongo's _id field.

Configuring Spring transactions support for MongoDB

I am using spring-boot-starter-data-mongodb:2.2.1.RELEASE and trying to add transaction support for Mongo DB operations.
I have the account service below, where documents are inserted into two collections accounts and profiles. In case an error occurs while inserting into profile collection, the insertion into accounts should rollback. I have configured Spring transactions using MongoTransactionManager.
#Service
public class AccountService {
#Transactional
public void register(UserAccount userAccount) {
userAccount = accountRepository.save(userAccount);
UserProfile userProfile = new UserProfile(userAccountDoc.getId());
userProfile = profileRepository.save(userProfile);
}
}
Enabled Spring transaction support for MongoDB.
#Configuration
public abstract class MongoConfig extends AbstractMongoConfiguration {
#Bean
MongoTransactionManager transactionManager(MongoDbFactory dbFactory) {
return new MongoTransactionManager(dbFactory);
}
}
As per Spring reference doc https://docs.spring.io/spring-data/mongodb/docs/2.2.1.RELEASE/reference/html/#mongo.transactions that is all required to enable transactions for MongoDB. But this is not working. Insertions into accounts collection are not rolled back in case error occurs while inserting into profiles collection. Any suggestions if I am missing anything?
I would use command monitoring or examine query logs on the server side to ensure that:
session ids are sent with queries/write commands
transaction operations are performed (there is no startTransaction command but there is a commitTransaction)

Table name configured with external properties file

I build a Spring-Boot application that accesses a Database and extracts data from it. Everything is working fine, but I want to configure the table names from an external .properties file.
like:
#Entity
#Table(name = "${fleet.table.name}")
public class Fleet {
...
}
I tried to find something but I didn't.
You can access external properties with the #Value("...") annotation.
So my question is: Is there any way I can configure the table names? Or can I change/intercept the query that is sent by hibernate?
Solution:
Ok, hibernate 5 works with the PhysicalNamingStrategy. So I created my own PhysicalNamingStrategy.
#Configuration
public class TableNameConfig{
#Value("${fleet.table.name}")
private String fleetTableName;
#Value("${visits.table.name}")
private String visitsTableName;
#Value("${route.table.name}")
private String routeTableName;
#Bean
public PhysicalNamingStrategyStandardImpl physicalNamingStrategyStandard(){
return new PhysicalNamingImpl();
}
class PhysicalNamingImpl extends PhysicalNamingStrategyStandardImpl {
#Override
public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
switch (name.getText()) {
case "Fleet":
return new Identifier(fleetTableName, name.isQuoted());
case "Visits":
return new Identifier(visitsTableName, name.isQuoted());
case "Result":
return new Identifier(routeTableName, name.isQuoted());
default:
return super.toPhysicalTableName(name, context);
}
}
}
}
Also, this Stackoverflow article over NamingStrategy gave me the idea.
Table names are really coming from hibernate itself via its strategy interfaces. Boot configures this as SpringNamingStrategy and there were some changes in Boot 2.x how things can be customised. Worth to read gh-1525 where these changes were made. Configure Hibernate Naming Strategy has some more info.
There were some ideas to add some custom properties to configure SpringNamingStrategy but we went with allowing easier customisation of a whole strategy beans as that allows users to to whatever they need to do.
AFAIK, there's no direct way to do config like you asked but I'd assume that if you create your own strategy you can then auto-wire you own properties to there. As in those customised strategy interfaces you will see the entity name, you could reserve a keyspace in boot's configuration properties to this and match entity names.
mytables.naming.fleet.name=foobar
mytables.naming.othertable.name=xxx
Your configuration properties would take mytables and within that naming would be a Map. Then in your custom strategy it would simply be by checking from mapping table if you defined a custom name.
Spring boot solution:
Create below class
#Configuration
public class CustomPhysicalNamingStrategy extends SpringPhysicalNamingStrategy{
#Value("${table.name}")
private String tableName;
#Override
public Identifier toPhysicalTableName(final Identifier identifier, final JdbcEnvironment jdbcEnv) {
return Identifier.toIdentifier(tableName);
}
}
Add below property to application.properties:
spring.jpa.properties.hibernate.physical_naming_strategy=<package.name>.CustomPhysicalNamingStrategy
table.name=product

Is it correct to access entity manager in entity class (to ensure no duplicate username)

I want to ensure no duplicate username's when validating my entity
/**
* #var string $name
*
* #ORM\Column(name="name", type="string", length=32)
* #Assert\NotBlank();
* #Assert\Callback(methods={"isUniqueUsername"})
*/
private $name;
I suppose I need to use the Validator Callback. Then I will have to query database for users with that username, so I require entity manager? How do I access it? And access it "correctly"? Do I use a validator class instead? It seems quite troublesome to create a class just to validate 1 field?
In symfony2, there is a Unique validator you can use to ensure that a username, email address or any other field is unique. When using annotations, it works like this:
use Symfony\Bridge\Doctrine\Validator\Constraints as Unique;
/**
* #ORM\Entity
* #ORM\Table
* #Unique\UniqueEntity(fields={"email"},message="Your email is already registered")
*/
class User{
// ...
}
When trying to add a user through a form, you should get the message stated in the Unique annotation.
You shouldn't use the entity manager from within an entity - it breaks Separation of Concerns. Your entity class is a POPO (plain old PHP object) that should simply describe an entity - it should not be responsible for anything persistence related. If you need to run a custom query, do so either from a repository class, or a separate service. The repository class already has access to the entity manager. If you use a custom service, simply inject the entity manager into it.

Resources