Bug 84 : @ManyToMany without foreign keys causes MatchingProperty not found Runtime Exception
Priority 
High
Reported Version 
 
Logged By 
William DeMoss
Status 
Fixed
Fixed Version 
1.1.0
Assigned To 
 
Product 
Ebean - core
Duplicate Of 
 
Created 
04/03/2009
Updated 
04/03/2009
Type 
Bug
 
Attachments 
No attachments

package eg;

import java.io.File;
import java.io.FilenameFilter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashSet;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.Table;

import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

import com.avaje.ebean.Ebean;
import com.avaje.ebean.ServerConfiguration;

public class ManyToManyTest {
static String base = "/tmp/ebean/many_to_many_test";
static String dictionary = base + "/dictionary";
static String logs = base + "/logs";

@BeforeClass
public static void setupClass() throws Exception {
createTables();
deleteCacheFiles();
registerServer();
}

private static void registerServer() {
ServerConfiguration configuration = new ServerConfiguration("ebean_test");
configuration.setDefaultServer(true);
configuration.setDataSourceDriver("com.mysql.jdbc.Driver");
configuration.setDataSourceUsername("root");
configuration.setDataSourcePassword("root");
configuration.setDataSourceUrl("jdbc:mysql://localhost:3306/ebean_test");
configuration.setDataSourceMaxConnections(100);

configuration.setProperty("ebean.log.directory", logs);
configuration.setProperty("ebean.log.level", "2");
configuration.setProperty("ebean.log.iud", "2");

configuration.setProperty("ebean.dictionary.directory", dictionary);
configuration.setProperty("ebean.autofetch.querytuning", "true");
configuration.setProperty("ebean.autofetch.profiling", "true");
configuration.setProperty("ebean.log.sqltoconsole", "false");

configuration.addEntity(Foo.class);
configuration.addEntity(Bar.class);

Ebean.registerServer(configuration);
}

private static void deleteCacheFiles() {
File file = new File(dictionary);
if (file.exists()) {
File[] files = file.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.startsWith(".ebean.");
}
});
for (File f : files) {
f.delete();
}
}
}

private static void createTables() throws ClassNotFoundException, SQLException {
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/ebean_test", "root", "root");
try {
connection.createStatement().execute("drop table if exists foo");
connection.createStatement().execute("create table foo (foo_id varchar(36), primary key(foo_id))");
connection.createStatement().execute("drop table if exists bar");
connection.createStatement().execute("create table bar (bar_id varchar(36), primary key(bar_id))");
connection.createStatement().execute("drop table if exists foo_bars");
connection.createStatement().execute("create table foo_bars (foo_id varchar(36), bar_id varchar(36), primary key(foo_id, bar_id))");
} finally {
connection.close();
}
}

@Test
public void test() throws Exception {
Foo foo = new Foo();
Ebean.save(foo);
final String fooId = foo.getId();
Assert.assertNotNull(fooId);

Bar bar = new Bar();
Ebean.save(bar);
Assert.assertNotNull(bar.getId());

foo.getBars().add(bar);
Ebean.save(foo);

Assert.assertFalse(Ebean.find(Foo.class, fooId).getBars().isEmpty());
System.out.println("done");
}

@Entity
@Table(name="foo")
public static class Foo {

@Id
@Column(name="foo_id")
@GeneratedValue(generator="auto.uuid")
String id;

@ManyToMany
@JoinTable(name="foo_bars", joinColumns=@JoinColumn(name="foo_id"), inverseJoinColumns=@JoinColumn(name="bar_id"))
Set bars = new HashSet();

public String getId() {
return this.id;
}

public void setId(String id) {
this.id = id;
}

public Set getBars() {
return this.bars;
}

public void setBars(Set bars) {
this.bars = bars;
}
}

@Entity
@Table(name="bar")
public static class Bar {

@Id
@Column(name="bar_id")
@GeneratedValue(generator="auto.uuid")
String id;

public String getId() {
return this.id;
}

public void setId(String id) {
this.id = id;
}
}

}

 
Rob 04 Mar 03:53
Unidirectional

Hmmm... just a quick note that this is unidirectional ... meaning that the relationship from "Bar" to "Foo" is not defined...

... unidirectional is generally fine ... except in this case where we are defining the relationships manually (not reading FK relationships from DB) ... then there is no explicit definition of how to get from "FooBar" to "Bar".

"Foo" -> "FooBar" is defined
"FooBar" -> "Bar" ... is not defined

... AKA could be derived as reverse of "Bar" -> "FoorBar" but there is not a @ManyToMany on "Bar"

Rob 04 Mar 11:19
inverseJoinColumns being ignored

This is definately a bug. Essentially Ebean is ignoring the inverseJoinColumns side...

Rob 04 Mar 12:27
WARNING: Save cascade on ManyToMany [bars]. ...

Note: This code near the end of the test raises a warning currently in Ebean

foo.getBars().add(bar);
Ebean.save(foo);

5/03/2009 1:21:56 AM com.avaje.ebean.server.persist.DefaultPersister saveAssocManyIntersection
WARNING: Save cascade on ManyToMany [bars]. The collection [class java.util.HashSet]was not a BeanCollection. The additions and removals can not be determined and *NO* inserts or deletes to the intersection table occured.


I Changed the code to:

Foo f = Ebean.find(Foo.class, fooId);
f.getBars().add(bar);
Ebean.save(f);

.. the set of Bar returned from the fetch... is a Ebean specific Set (BeanCollection) that keeps track of inserts and deletes from the Set (to translate that into inserts/deletes from the intersection table).

Hmmm.

Rob 04 Mar 12:28
Anyways...

I have a fix for this bug... but need to do a little more testing before I commit it up into HEAD.

Rob 07 Mar 02:47
Fixed in HEAD

Fixed in HEAD.

woResponse

Upload a file