I have a class as follows:
package mongo;
import com.mongodb.BasicDBObject;
public class tweet extends BasicDBObject{
private String name;
private Integer id;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
}
Now I am trying to insert the object of this class into mongodb:
public void connect() throws UnknownHostException
{
MongoClient mongoClient = new MongoClient( "localhost" , 27017 );
DB db = mongoClient.getDB( "test" );
tweet t=new tweet();
t.setId(100);
t.setName("Gggggg");
DBCollection Collection = null ;
Collection = db.getCollection("test");
DBObject doc = new BasicDBObject();
doc.put("first", t);
Collection.save( doc);
System.err.println(Collection.findOne());
}
But when I run this code, the object t is not inserted to the db and the system.err.println return the following:
{ "_id" : { "$oid" : "546c00efbadcd42088c8fee3"}}
How can I add the object into mongodb? Is it possible to do that?
BasicDBObject is actually a LinkedHashMap. So when you extend it, you need to put and retrieve values as you do it in a map.
So simply setting the values as attributes doesn't make sense, those attributes need to be put inside the Tweet map.
You need to modify your Tweet Class as follows:
class Tweet extends BasicDBObject{
public String getName() {
return (String)this.get("name");
}
public void setName(String name) {
this.put("name", name);
}
public Integer getId() {
return (Integer)this.get("_id");
}
public void setId(Integer id) {
this.put("_id", id);
}
}
and your main method as:
MongoClient mongoClient = new MongoClient( "localhost" , 27017 );
DB db = mongoClient.getDB( "test" );
Tweet t=new Tweet();
t.setId(100);
t.setName("Ghorbani");
DBCollection collection = null ;
collection = db.getCollection("test");
collection.save(t);
System.err.println(collection.findOne());
If you notice,collection.save(t);, the Tweet object is being directly saved. That is how it should work.
o/p:
{ "_id" : 100 , "name" : "Ghorbani"}
You seem to be trying to set an ID for your object. Usually that is something that is done automatically from Mongo. Try removing t.setId(100); and then run your code again.
Tip - try using Mongoose to manage your connection to Mongo.
Related
I connected my Spring-Boot-Application to a MongoDB. The application is nothing serious, just for getting into working with spring and MongoDB.
The problem it, that my id is a String and I get an Internal Server Error, when I pass the id of a database entry, in order to get it byId...
This is my domain class:
#Data
#NoArgsConstructor
#AllArgsConstructor
#Builder
#Document(collection = "songinfo")
public class SongInfo {
#Id
private String id;
private int songId;
private String songName;
private String description;
}
The Controller-Method:
#RequiredArgsConstructor
#RestController
#RequestMapping("/songsinfo")
public class SongsInfoController {
private final SongInfoService songInfoService;
#GetMapping(value = "/{id}", headers = "Accept=application/json", produces =
{MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<SongInfo> getSongInfoById(#PathVariable(value = "id") String id) {
SongInfo songInfo = songInfoService.getSongInfoById(id);
if (songInfo == null)
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
return new ResponseEntity<>(songInfo, HttpStatus.OK);
}
The SongInfoServiceImpl:*
#Override
public SongInfo getSongInfoById(String id) {
return songInfoRepository.findById(id).orElseThrow(NotFoundException::new);
}
This is the SongsInfoRepository:
public interface SongInfoRepository extends MongoRepository<SongInfo, String> {
}
Getting all songinfos from the database is working fine:
But when is pass the id from one of these entries, I get this:
What is wrong here with my implementation?
You're throwing the exception in SongInfoServiceImpl which is not handled in your SongsInfoController Class.
Solution 1: Instead of throwing the exception return null.
SongInfoServiceImpl.java
#Override
public SongInfo getSongInfoById(String id) {
return songInfoRepository.findById(id).orElse(null);
}
Solution 2: Add try catch block
SongsInfoController.java
#RequiredArgsConstructor
#RestController
#RequestMapping("/songsinfo")
public class SongsInfoController {
private final SongInfoService songInfoService;
#GetMapping(value = "/{id}",
headers = "Accept=application/json",
produces = {MediaType.APPLICATION_JSON_VALUE}
)
public ResponseEntity<SongInfo> getSongInfoById(#PathVariable(value = "id") String id) {
SongInfo songInfo = null;
try {
songInfo = songInfoService.getSongInfoById(id);
} catch(Exception e) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
return new ResponseEntity<>(songInfo, HttpStatus.OK);
}
}
I think you need to divide two problem.
Check id parameter SongsInfoController
Inside controller check your parameter is valid through log or sysout
Check getSongInfoById method in SongInfoServiceImpl
Simply getSongInfoById(8752); is get error?
I want to add comment but my reputation is under 50.
If you comment above two solution check result, then I will add additional answer.
Mongo runtime throws following error when trying to insert a new document in the collection. Note that the database and collection does NOT exist yet (and my assumption is that mongo runtime will create the database, followed by collection and then insert my first document by converting my POJO to BSON using a default codec). Any suggestions?
Error: 2015-12-13 18:46:46,384 - application-akka.actor.default-dispatcher-3 - [error] - application - Can't find a codec for class models.User.
/* Model Class */
package models;
import javax.persistence.Id;
import org.mongojack.ObjectId;
public class User {
#ObjectId #Id public String _id;
public String firstname;
public String lastname;
public String email;
public String phone;
public String address;
}
/* Controller Class */
public class Users extends Controller {
#BodyParser.Of(BodyParser.Json.class)
public Result create() {
Logger.info("Enter - Users::create()");
try {
Form<User> user = Form.form(User.class).bindFromRequest();
if (user.hasErrors()) {
Logger.info("User: " + user.toString());
Logger.info(user.errorsAsJson().toString());
return badRequest(user.errorsAsJson());
}
else {
User oneUser = user.get();
MongoClient mongoClient= new MongoClient();
MongoDatabase db = mongoClient.getDatabase("marketplace");
MongoCollection<User> col = db.getCollection("users", User.class);
col.insertOne(oneUser);
mongoClient.close();
return ok();
}
}
catch (Exception e) {
Logger.error(e.getLocalizedMessage());
return internalServerError(e.getMessage());
}
finally {
Logger.info("Exit - Users::create()");
}
}
}
You will have to wrap the MongoCollection with a JacksonDBCollection to enable all the MongoJack features.
Something like this:
JacksonDBCollection<User, String> userColl = JacksonDBCollection.wrap(col, User.class, String.class);
And then use userColl to insert your new object.
It seems that this the only way supported at this time. This requires using the getDB() method, which is deprecated since the mongo driver 3.0.
See the relevant issue on github here:
https://github.com/mongojack/mongojack/issues/105
Yup - this works. However I had to use deprecated getDB since it has a getter that returns collection of type DBCollection which "wrap" function expects. I was hoping to use getDatabase instead but gets me MongoCollection which Jackson's "Wrap" won't accept. Any suggestions?
User oneUser = user.get();
MongoClient mongoClient= new MongoClient();
DB db = mongoClient.getDB("marketplace");
DBCollection col = db.getCollection("marketplace");
JacksonDBCollection<User, String> userCol = JacksonDBCollection.wrap(col,User.class, String.class);
userCol.insert(oneUser);
I am still somewhat of a novice with Spring Boot and Spring Data Rest and hope someone out there with experience in Accessing by Property. Since I cannot change a database which stores types for Letters in an unnormalized fashion (delimited string in a varchar), I thought that I could leverage some logic in properties to overcome this. However I notice that when using property access, some of my getters are never called.
My Model code:
package ...
import ...
#Entity
#Table(name="letters", catalog="clovisdb")
#Access(AccessType.PROPERTY)
public class Letter {
public enum PhoneticType {
VOWEL, SHORT, LONG, COMMON;
public static boolean contains(String s) { ... }
}
public enum PositionType {
ALL, INITIAL, MEDIAL, FINAL;
public static boolean contains(String s) { ... }
}
public enum CaseType {
ALL, LOWER, UPPER;
public static boolean contains(String s) { ... }
}
private int id;
private String name;
private String translit;
private String present;
private List<PhoneticType> phoneticTypes;
private CaseType caseType;
private PositionType positionType;
#Id
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getTranslit() { return translit; }
public void setTranslit(String translit) { this.translit = translit; }
public String getPresent() { return present; }
public void setPresent(String present) { this.present = present; }
public String getTypes() {
StringBuilder sb = new StringBuilder(); //
if (phoneticTypes!=null) for (PhoneticType type : phoneticTypes) sb.append(" ").append(type.name());
if (caseType!=null) sb.append(" ").append(caseType.name());
if (positionType!=null) sb.append(" ").append(positionType.name());
return sb.substring( sb.length()>0?1:0 );
}
public void setTypes(String types) {
List<PhoneticType> phoneticTypes = new ArrayList<PhoneticType>();
CaseType caseType = null;
PositionType positionType = null;
for (String val : Arrays.asList(types.split(" "))) {
String canonicalVal = val.toUpperCase();
if (PhoneticType.contains(canonicalVal)) phoneticTypes.add(PhoneticType.valueOf(canonicalVal));
else if (CaseType.contains(canonicalVal)) caseType = CaseType.valueOf(canonicalVal);
else if (PositionType.contains(canonicalVal)) positionType = PositionType.valueOf(canonicalVal);
}
this.phoneticTypes = phoneticTypes;
this.caseType = (caseType==null)? CaseType.ALL : caseType;
this.positionType = (positionType==null)? PositionType.ALL : positionType;
}
#Override
public String toString() { .... }
}
My Repository/DAO code:
package ...
import ...
#RepositoryRestResource
public interface LetterRepository extends CrudRepository<Letter, Integer> {
List<Letter> findByTypesLike(#Param("types") String types);
}
Hitting this URI: http://mytestserver.com:8080/greekLetters/6
and setting breakpoints on all the getters and setters, I can see that the properties are called in this order:
setId
setName
setPresent
setTranslit
setTypes
(getId not called)
getName
getTranslit
getPresent
(getTypes not called !!)
The json returned for the URI above reflects all the getters called, and there are no errors
{
"name" : "alpha",
"translit" : "`A/",
"present" : "Ἄ",
"_links" : {
"self" : {
"href" : "http://mytestserver.com:8080/letters/6"
}
}
}
But why is my getTypes() not being called and my JSON object missing the “types” attribute? I note that the setter is called, which makes it even stranger to me.
Any help would be appreciated!
Thanks in advance
That's probably because you don't have a field types, so getTypes() isn't a proper getter. Try adding this to your entity
#Transient
private String types;
I don't know how the inner works, but it's possible that the class is first scanned for its fields, and then a getter is called for each field. And since you don't have types field, the getter isn't called. Setter getting called could be a feature but I wouldn't be surprised if it is a bug, because findByTypesLike should translate to find Letters whose types field is like <parameter>, and types is not a field.
Another thing you can try, is to annotate that getter with #JsonInclude. Jackson 2 annotations are supported in Spring versions 3.2+ (also backported to 3.1.2).
so I have a POJO object that I am creating and saving to a MongoDB collection using Jongo:
import java.util.Map;
public class MyObject {
private String name;
private Map<String, String> mappings;
public MyObject() {
}
public MyObject(String name, Map mappings) {
this.name = name;
this.mappings = mappings;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Map<String, String> getMappings() {
return mappings;
}
public void setMappings(Map<String, String> mappings) {
this.mappings = mappings;
}
}
Test class for saving objects to mongo:
import com.mongodb.DB;
import com.mongodb.MongoClient;
import org.jongo.Jongo;
import org.jongo.MongoCollection;
import java.net.UnknownHostException;
import java.util.HashMap;
public class NullFieldTest {
public static void main(String[] args) throws UnknownHostException {
DB db = new MongoClient("localhost", 27017).getDB("testDB") ;
Jongo jongo = new Jongo(db);
MongoCollection testCollection = jongo.getCollection("testCollection");
MyObject objectA = new MyObject("objectA", new HashMap());
MyObject objectB = new MyObject("objectB", null);
testCollection.save(objectA);
testCollection.save(objectB);
}
}
This test saves the objects fine:
{
"_id" : ObjectId("543cf1a6cd8936deafcf66cf"),
"name" : "objectA",
"mappings" : {}
}
{
"_id" : ObjectId("543cf1a6cd8936deafcf66d0"),
"name" : "objectB"
}
but what I really want is for the null mappings in objectB to appear as
"mappings" : null
I know that a field within a collection can have a null value, but I dont know how to do this with the jongo driver, any ideas?
FYI, I'm using jongo V1.1
problem solved, my issue was in the serialization of the objects, added an annotation from Jackson to include even null fields:
#JsonInclude(JsonInclude.Include.ALWAYS)
public class MyObject {
Found more details on this post:
How to tell Jackson to ignore a field during serialization if its value is null?
Jongo comes with a pre-configured Jackson which ignore null fields : https://github.com/bguerout/jongo/blob/443b461e47acdcffb9f51efafb291b7e0c805c26/src/main/java/org/jongo/marshall/jackson/configuration/VisibilityModifier.java
You can change this configuration by creating a custom Mapper using JacksonMapper.Builder :
Mapper mapper = new JacksonMapper.Builder().addModifier(new MapperModifier() {
public void modify(ObjectMapper mapper) {
mapper.setSerializationInclusion(JsonInclude.Include.ALWAYS);
}
}).build();
Jongo jongo = new Jongo(db, mapper);
Just getting into the NoSQL stuff so forgive me if this is a simple question. I am trying to somewhat implement a repository type pattern using a generic repository for the more common operations.
One thing that I have run into that is killing this idea is that in order to get the collection you plan to work with you have to pass a string value for the name of the collection.
var collection = database.GetCollection<Entity>("entities");
This means that I have to hard code my collection names or code up a dictionary somewhere to act as a lookup so that i can map the object type to a collection name.
How is everyone else handling this?
What you can do is "semi-hardcode." You can put the name of the collection in a class name and refere to it:
public class Entity {
public static readonly string Name = "entities";
}
var collection = database.GetCollection<Entity>(Entity.Name);
I wrote a class to manage DB transactions
First you need a base class for all entities:
public abstract class Entity
{
public ObjectId Id { set; get; }
}
then an static class to manage all:
public static class MongoDB
{
private static string connectionString = "mongodb://localhost";
public static string DatabaseName { get { return "test"; } }
private static MongoServer _server;
private static MongoDatabase _database;
public static MongoServer Server
{
get
{
if (_server == null)
{
var client = new MongoClient(connectionString);
_server = client.GetServer();
}
return _server;
}
}
public static MongoDatabase DB
{
get
{
if(_database == null)
_database = Server.GetDatabase(MongoDB.DatabaseName);
return _database;
}
}
public static MongoCollection<T> GetCollection<T>() where T : Entity
{
return DB.GetCollection<T>(typeof(T).FullName);
}
public static List<T> GetEntityList<T>() where T : Entity
{
var collection = MongoDB.DB.GetCollection<T>(typeof(T).FullName);
return collection.FindAll().ToList<T>();
}
public static void InsertEntity<T>(T entity) where T : Entity
{
GetCollection<T>().Save(entity);
}
}
then use it like this:
public class SomeEntity : Entity { public string Name {set;get;} }
MongoDB.InsertEntity<SomeEntity>(new SomeEntity(){ Name = "ashkan" });
List<SomeEntity> someEntities = MongoDB.GetEntityList<SomeEntity>();
I finally found an approach very usefull for me as all my mongo collections follow a camel case underscore naming convention, so I made a simple string extension to translate the POCO naming convention to my mongo convention.
private readonly IMongoDatabase _db;
public IMongoCollection<TCollection> GetCollection<TCollection>() =>
_db.GetCollection<TCollection>(typeof(TCollection).ToString().MongifyToCollection());
This method is inside a class made for handling mongo using dependency injection and it also wraps the default GetCollection to make it a bit more OO
public class MongoContext : IMongoContext
{
private readonly IMongoDatabase _db;
public MongoContext()
{
var connectionString = MongoUrl.Create(ConfigurationManager.ConnectionStrings["mongo"].ConnectionString);
var client = new MongoClient(connectionString);
_db = client.GetDatabase(connectionString.DatabaseName);
RegisterConventions();
}
public IMongoCollection<TCollection> GetCollection<TCollection>() =>
_db.GetCollection<TCollection>(typeof(TCollection).Name.MongifyToCollection());
...
And the extension:
// It may require some improvements, but enough simple for my needs at the moment
public static string MongifyToCollection(this string source)
{
var result = source.Mongify().Pluralize(); //simple extension to replace upper letters to lower, etc
return result;
}