I have a specific requirement needed in my project is to store logging data in my MongoDB database. There are lots of blogs for storing logs in a relational database but I can't find anything that works with MongoDB.
After hours of searching, I found this Wordpress article but after implementing it nothing happened. Blog: https://assylias.wordpress.com/2013/03/22/a-simple-logback-appender-for-mongodb/?unapproved=1424&moderation-hash=a5ff2a0d2832b77e2d7c0be3173ea667#comment-1424
But it's not working
Problem: I need to persist the log data to MongoDB.
Does anyone know how to append log data into MongoDB with Spring Boot?
Edit: I've figured a way around how to do it but it can be done with any type of database no matter MySQL or MongoDB. I'm providing the answer to how I did it but the question is still open. If anyone knows how to do it feel free to answer it and if it works I will accept the answer.
So the trick here is making a custom method that returns a string to the Logger class and saves the data to the database(any database relational or NoSQL doesn't matter).
I will try to explain the whole scenario:
This is a document that will store the data to the MongoDB
#Document
public class Logs {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private String id;
private Date date;
private String level;
private String message;
//getters and setters
}
If you are using MySQL you can use #Entity. Then create a MongoRepository of this class or CrudRepository if using JPA
public interface LogsRepository extends MongoRepository<Logs, String> {
}
Now you have to make a CustomLogger class which is used to insert data to the database
#Component
public class CustomLogger{
#Autowired
MongoTemplate mongoTemplate;
public String info(String message) {
Logs logs = new Logs();
logs.setLevel("INFO");
logs.setMessage(message);
logs.setDate(new Date());
mongoTemplate.insert(loggerDetail);
return message;
// same for other methods like debug(), error(), etc
}
Here I used MongoTemplate instead of LogsRepostiry to save data because Mongo allow to insert data if extends MongoRepository of every class
Now all you have to do is autowire this component to the class where you are using the logger. For my case it was a controller. When I'm hitting an API logs will show in console and also will save to the database
#RestController
public class LogsController {
#Autowired
CustomLogger customLogger;
Logger logger = LoggerFactory.getLogger(CustomLogger.class);
#GetMapping("/logger")
public String basicControllerToSaveData() {
logger.info(customLogger.info("Saving Logs to database"));
return "Success";
}
}
This will do the trick!
Related
I would like my MongoRepository in Spring Boot to automatically delete documents at a certain point in time after creation. Therefore I created the following class:
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.index.Indexed;
public class MyDocument {
#Id
private String id;
#Indexed
private String name;
#Indexed(expireAfterSeconds = 0)
private LocalDateTime deleteAt;
}
Then, I save it in the Spring Boot MongoRepository:
MyDocument doc = modelMapper.map(myDocumentDto, MyDocument.class);
LocalDateTime timePoint = LocalDateTime.now();
timePoint = timePoint.plusMinutes(1L);
doc.setDeleteAt(timePoint);
docRepository.save(doc);
I periodically query the repository and would assume that after one minute, the document will not be there anymore. Unfortunately, I get the document every time I query and it is never deleted.
What am I doing wrong?
The document is persisted as follows (.toString()):
MyDocument{id='5915c65a2e9b694ac8ff8b67', name='string', deleteAt=2017-05-12T16:28:38.787}
Is MongoDB possibly unable to read and process the LocalDateTime format? I'm using org.springframework.data:spring-data-mongodb:1.10.1.RELEASE, so JSR-310 should already be supported as announced in 2015 here: https://spring.io/blog/2015/03/26/what-s-new-in-spring-data-fowler
I could fix the issue:
First of all, java.time.LocalDateTime is no problem with Spring Data / MongoDB.
For clarification, add a name to an index, e.g. #Indexed(name = "deleteAt", expireAfterSeconds = 0). This step might not be needed though. However, adding the #Document annotation helped a lot:
#Document(collection = "expiringDocument")
When I delete the whole collection while my application is running, a new document insert will create the collection again but without the indexes. To ensure indexes are created, restart the application.
What is the best way to check if a record exists and if it doesn't, create it (avoiding duplicates)?
Keep in mind that this is a distributed application running across many application servers.
I'm trying to avoid these:
Race Conditions
TOCTOU
A simple example:
Person.java
#Entity
public class Person {
#Id
#GeneratedValue
private long id;
private String firstName;
private String lastName;
//Getters and Setters Omitted
}
PersonRepository.java
public interface PersonRepository extends CrudRepository<Person, Long>{
public Person findByFirstName(String firstName);
}
Some Method
public void someMethod() {
Person john = new Person();
john.setFirstName("John");
john.setLastName("Doe");
if(personRepo.findByFirstName(john.getFirstName()) == null){
personRepo.save(john);
}else{
//Don't Save Person
}
}
Clearly as the code currently stands, there is a chance that the Person could be inserted in the database in between the time I checked if it already exists and when I insert it myself. Thus a duplicate would be created.
How should I avoid this?
Based on my initial research, perhaps a combination of
#Transactional
#Lock
But the exact configuration is what I'm unsure of. Any guidance would be greatly appreciated. To reiterate, this application will be distributed across multiple servers so this must still work in a highly-available, distributed environment.
For Inserts: if you want to prevent same recordsto be persisted, than you may want to take some precoutions on DB side. In your example, if firstname should be unique, then define a unique index on that column, or a agroup of colunsd that should be unique, and let the DB handle the check, you just insert & get exception if you're inserting a record that's already inserted.
For updates: use #Version (javax.persistence.Version) annotation like this:
#Version
private long version;
Define a version column in tables, Hibernate or any other ORM will automatically populate the value & also verison to where clause when entity updated. So if someone try to update the old entity, it prevent this. Be careful, this doesn't throw exception, just return update count as 0, so you may want to check this.
working with OpenJPA2 persistence. I have a very simple entity class, that does have a String property and a List property. I do persist its instances flawlessly with the nested List (in a JSF2 web project). I check the database and there appears two tables (I use automatic schema generation), one for the entity itself, and other table for the nested List. All data persisted using EntityManager is stored fine on both tables.
Problem is I cannot retrieve nested data. I mean, I do a Query for getting all instances of the entity, but the List of all instances come empty.
(DB Engine is MySQL. ORM is OpenJPA2. Server is TomEE 1.6. IDE is Netbeans 8. I use automatic schema generation, so I DO NOT WANT to design the database tables, and I DO WANT to let the ORM create the tables, so I can work purely with objects and forget about DB.)
following is Entity Class code:
#Entity
public class Cliente implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String nombre;
#ElementCollection
private List<String> emails = new ArrayList<String>();
// getters and setters omitted for brevity.
It does have an associated Facade Class, which is ClienteFacade. It includes the getAll method which uses a JPQL Query.
public List<Cliente> listaClientes(){
Query query = em.createQuery("SELECT c FROM Cliente c");
return query.getResultList(); }
Problem is, I get all instances of the entity in a List, but all lists of List emails come out empty. I think problem may be in the JPQL query. Still trying to learn JPQL with some difficulty... so please, How can I retrieve the nested data with JPQL?
Many thanks!
Try #ElementCollection(fetch = FetchType.EAGER) because the default type is lazy. That means that the lists are not loaded directly.
I'm using spring data (jpaRepository) + Oracle 11g Database.
Here's the code of my JUnit test:
#Test
public void testAjoutUtilisateur() {
Utilisateur utilisateur = new Utilisateur();
(...)
utilisateur=repository.save(utilisateur);
Utilisateur dbutilisateur = repository.findOne(utilisateur.getIdutilisateur());
assertNotNull(dbutilisateur);
When I debug I find that "utilisateur" object returned by repository.save method has an id like "2100" while the corresponding inserted line in the database have an id like "43".
I have an Oracle database with a sequence and a trigger to have the auto incremented property for the id for my "Utilisateur" table.
Here is the id definition in my Utilisateur entity:
#Entity
#NamedQuery(name="Utilisateur.findAll", query="SELECT u FROM Utilisateur u")
#SequenceGenerator(sequenceName="ID_UTILISATEUR_SEQ", name="ID_UTILISATEUR_SEQ")
public class Utilisateur implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="ID_UTILISATEUR_SEQ")
private Long idutilisateur;
Where is the problem? Is it within the save method?
Thank you.
Edit:
I figured out that the problem was already solved by the solution of #jhadesdev and the data lines I was talking about were inserted when the triggers were actives.
Finally, I have to mention that by default the JUnit test seems to not insert data in the database (it inserts then rollback). In order to invalidate this behaviour we have to specify the #TransactionConfiguration(defaultRollback=false) annotation in the test class.
For example (in my case):
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:context/dao-context.xml" })
#TransactionConfiguration(defaultRollback=false)
#Transactional
public class UtilisateurRepositoryTest {
Hope it can help someone.
The problem is that two separate mechanisms are in place to generate the key:
one at Hibernate level which is to call a sequence and use the value to populate an Id column and send it to the database as the insert key
and another mechanism at the database that Hibernate does not know about: the column is incremented via a trigger.
Hibernate thinks that the insert was made with the value of the sequence, but in the database something else occurred. The simplest solution would probably be to remove the trigger mechanism, and let Hibernate populate the key based on the sequence only.
I'm experimenting with play (v2.2.2), and I have it connected to MongoDB (v2.4.6) using jackson.
I have a models.Role class with the following attributes:
#Id
#ObjectId
public String id;
public String name;
public ArrayList<String> permissions;
On the template (roles.scala.html), I can easily get the list of permissions to be printed on the HTML, but when I try to add a new role passing a single permission as a string in the form as an #InputText field, it does not get recorded in MongoDB. I suppose that it is because play/scala is trying to assign a simple String to an ArrayList<String>.
Any ideas on the propper approach? Maybe I should do some logic on the create() method under Role class?