Announcement Announcement Module
Collapse
No announcement yet.
MongoDB in queries using DBRefs not working Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • MongoDB in queries using DBRefs not working

    I am trying to query a MongoDB repository using in criteria on a DBRef. I gathered from the documentation - and by trying - that this isn't supported using the method name to query mapping framework. I am now trying to do this using a custom method and implementation, however, this too isn't working.

    Domain objects:
    Code:
    public class Automaker implements Serializable {
    
    	private static final long serialVersionUID = 1L;
    	
    	private String id;
    	
    	private String name;
    
    	public Automaker() {
    		super();
    	}
    	
    	public Automaker(final String name) {
    		super();
    		this.name = name;
    	}
    
    	public String getId() {
    		return id;
    	}
    
    	public void setId(String id) {
    		this.id = id;
    	}
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    	
    }
    
    public class Car implements Serializable {
    
    	private static final long serialVersionUID = 1L;
    
    	@Id
    	public String id;
    	
    	@Indexed(unique=true)
    	private String model;
    	
    	@DBRef
    	private Automaker automaker;
    	
    	public Car() { }
    	
    	public Car(final Automaker make, final String model) {
    		this.automaker = make;
    		this.model = model;
    	}
    
    	public String getId() {
    		return id;
    	}
    
    	public void setId(String id) {
    		this.id = id;
    	}
    
    	public String getModel() {
    		return model;
    	}
    
    	public void setModel(String model) {
    		this.model = model;
    	}
    
    	public Automaker getAutomaker() {
    		return automaker;
    	}
    
    	public void setAutomaker(Automaker automaker) {
    		this.automaker = automaker;
    	}
    	
    }
    Repository:
    Code:
    @Repository
    public interface CarRepository extends MongoRepository<Car, String>, CustomCarRepository {
    
    	public List<Car> findByAutomaker(Automaker make);
    	
    	public List<Car> findByAutomakerIn(Automaker... makers);
    	
    }
    
    public interface CustomCarRepository {
    	public List<Car> findCarsByAutomakers(Automaker... makers);
    }
    
    public final class CarRepositoryImpl implements CustomCarRepository {
    	
    	@Autowired
    	MongoTemplate template;
    
    	@Override
    	public List<Car> findCarsByAutomakers(Automaker... makers) {
    		
    		Query q = new Query(new Criteria("automaker").in( (Object[]) makers));
    		
    		return template.find(q, Car.class);
    	}
    
    }
    Unit tests:
    Code:
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = "/repositories.xml")
    public final class CarRepositoryTest {
    
    	@Autowired
    	AutomakerRepository automakerRepository;
    	
    	@Autowired
    	CarRepository carRepository;
    	
    	private Automaker ford;
    	private Automaker honda;
    	private Automaker toyota;
    	
    	@Before
    	public void setUp() throws Exception {
    		
    		ford = automakerRepository.save(new Automaker("Ford"));
    		honda = automakerRepository.save(new Automaker("Honda"));
    		toyota = automakerRepository.save(new Automaker("Toyota"));
    		
    		carRepository.save(new Car(ford, "Explorer"));
    		
    		carRepository.save(new Car(honda, "Accord"));
    		carRepository.save(new Car(honda, "Civic"));
    		carRepository.save(new Car(honda, "Pilot"));
    		
    		carRepository.save(new Car(toyota, "Camry"));
    		carRepository.save(new Car(toyota, "Yaris"));
    		carRepository.save(new Car(toyota, "Highlander"));
    	}
    
    	@After
    	public void tearDown() throws Exception {
    		carRepository.deleteAll();
    		automakerRepository.deleteAll();
    	}
    
    	@Test
    	public void testFindByAutomaker() {
    		assertEquals(1, carRepository.findByAutomaker(ford).size());
    		assertEquals(3, carRepository.findByAutomaker(honda).size());
    		assertEquals(3, carRepository.findByAutomaker(toyota).size());
    	}
    	
    	@Test
    	public void testFindByAutomakerIn() {
    		assertEquals(4, carRepository.findByAutomakerIn(ford, toyota));
    	}
    	
    	@Test
    	public void testFindCarsByAutomakers() {
    		assertEquals(4, carRepository.findCarsByAutomakers(ford, honda));
    	}
    
    }
    Both testFindByAutomakerIn() and testFindCarsByAutomakers() fail. The former throws an exception:
    Code:
    org.springframework.data.mapping.model.MappingException: No id property found on class class [Lnet.rossillo.car.domain.Automaker;
    	at org.springframework.data.mongodb.core.convert.MappingMongoConverter.createDBRef(MappingMongoConverter.java:688)
    	at org.springframework.data.mongodb.core.convert.MappingMongoConverter.toDBRef(MappingMongoConverter.java:281)
    	at org.springframework.data.mongodb.repository.query.ConvertingParameterAccessor$ConvertingIterator.nextConverted(ConvertingParameterAccessor.java:161)
    	at org.springframework.data.mongodb.repository.query.MongoQueryCreator.nextAsArray(MongoQueryCreator.java:266)
    	at org.springframework.data.mongodb.repository.query.MongoQueryCreator.from(MongoQueryCreator.java:200)
    	at org.springframework.data.mongodb.repository.query.MongoQueryCreator.create(MongoQueryCreator.java:102)
    	at org.springframework.data.mongodb.repository.query.MongoQueryCreator.create(MongoQueryCreator.java:47)
    	at org.springframework.data.repository.query.parser.AbstractQueryCreator.createCriteria(AbstractQueryCreator.java:109)
    	at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:88)
    	at org.springframework.data.repository.query.parser.AbstractQueryCreator.createQuery(AbstractQueryCreator.java:73)
    	at org.springframework.data.mongodb.repository.query.PartTreeMongoQuery.createQuery(PartTreeMongoQuery.java:69)
    	at org.springframework.data.mongodb.repository.query.AbstractMongoQuery.execute(AbstractMongoQuery.java:77)
    	at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.invoke(RepositoryFactorySupport.java:313)
    	at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    	at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202)
    	at $Proxy25.findByAutomakerIn(Unknown Source)
    	at net.rossillo.car.repository.CarRepositoryTest.testFindByAutomakerIn(CarRepositoryTest.java:62)
    The latter returns an emptry array because the generated query criteria is incorrect. I posted the code, along with a Gradle build script to GitHub.

    Has anyone found a work around or is there a solution to this?

    Thanks in advance,
    Scott

  • #2
    Possible solution for using in criteria with DBRefs.

    I found a workable solution to doing $in queries with DBRefs. The solution uses a custom interface to declare the method with in criteria and custom implementation (below) to parse out the incoming DBRef objects and convert them to a collection of ObjectIds to be set as query criteria.

    Code:
    package net.rossillo.car.repository;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import net.rossillo.car.domain.Automaker;
    import net.rossillo.car.domain.Car;
    
    import org.bson.types.ObjectId;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.mongodb.core.MongoTemplate;
    import org.springframework.data.mongodb.core.query.Criteria;
    import org.springframework.data.mongodb.core.query.Query;
    
    public final class CarRepositoryImpl implements CustomCarRepository {
    	
    	@Autowired
    	MongoTemplate template;
    
    	@Override
    	public List<Car> findCarsByAutomakers(Automaker... makers) {
    		
    		final List<ObjectId> idList = new ArrayList<ObjectId>(makers.length);
    		
    		for(final Automaker maker : makers) {
    			idList.add(new ObjectId(maker.getId()));
    		}
    		
    		Query q = new Query(new Criteria("automaker.$id").in(idList));
    		
    		return template.find(q, Car.class);
    	}
    
    }
    I'm posting this update here and I updated the code on GitHub to use this working solution in case it helps others with the same problem.

    I'd still be interested to hear if there is a simpler way to do this.
    Last edited by d3xt3r; Aug 20th, 2012, 09:42 PM. Reason: Updated GitHub workaround link.

    Comment


    • #3
      Would you mind trying a recent snapshot build? I've created and fixed a few tickets covering these issues [0, 1].

      [0] https://jira.springsource.org/browse/DATAMONGO-505
      [1] https://jira.springsource.org/browse/DATAMONGO-511

      Comment


      • #4
        I tried the latest snapshots, still no luck. I pushed an update to master on GitHub with the broken test case.

        Comment


        • #5
          I've created and fixed DATAMONGO-517 [0] and fixed some glitches in your sample code to get the tests working. I've issued a pull request [1] with the necessary changes.

          [0] https://jira.springsource.org/browse/DATAMONGO-517
          [1] https://github.com/foo4u/spring-data-queries/pull/1

          Comment


          • #6
            Oliver, thank you for your help and the bug fix, this works great now. I have a more complex use-case to test out now, I'll reply here how that goes.

            Thanks again.

            Comment

            Working...
            X