During my domain model I've got a bi-directionnel association between your ProductList entity and also the Product entity using the following hibernate mapping :

@Entity @Indexed
@Table(name="product_list")
public class ProductList {

@ManyToMany(fetch=FetchType.LAZY)
@JoinTable(name = "list_items",
        inverseJoinColumns = { @JoinColumn(name = "product_id")},
        joinColumns = { @JoinColumn(name = "list_id")})
@IndexColumn(name = "item_index", base = 1, nullable = false )
@LazyCollection(LazyCollectionOption.EXTRA)
@BatchSize(size=50)
private List<Product> products = new LinkedList<Product>();
....

}

@Entity
@Table(name="logical_item")
@Cache(usage=CacheConcurrencyStrategy.READ_WRITE)
public class Product {

@ManyToMany(fetch=FetchType.LAZY, mappedBy="products")
private Set<ProductList> productLists = new LinkedHashSet<ProductList>();

...
}

However when I attempted to include an item to some persistent productList Hibernate attempt to load all of the product within the list before ! I've a lot more than 14 000 items in a listing !

Product item = (Product) session.get(Product.class, 123);
ProductList myFavoriteItems = (ProductList) session.get(ProductList.class, 321);

// Evil lazy loading (need more 512Mo of memory )
myFavoriteItems.addItem(Product item);

public void addItem(Product item){
    this.getProducts().add(item);
    item.getProductLists().add(this);
}

How you can give a product in a listing without loading all of the database ?

i suppose it is among the disadvantages of utilizing a ManyToMany relation when you really need to update the join table.

I'd advise creating an entity from the join table, and you would only needed to produce the join entity and save it :

public class ProductListItem {
    @ManyToOne(...)
    private Product product;

    @ManyToOne(...)
    private ProductList productList;

    ...
}

And also you could have a transient getter than would return an item list from the product :

public class Product {

    @OneToMany(...)
    private Set<ProductListItem> items;

    @Transient
    public Set<ProductList> getProductLists() {
        Set<ProductList> list = new LinkedHashSet<ProductList>();
        for(ProductListItem item : items) {
            list.add(item.getProductList());
        }
        return Collections.unmodifiableSet(list);
    }
    ...
}

Same factor for sleep issues from the manytomany relation.

Then, your save operation is only a matter of developing a ProductListItem and saving it, that will load nothing, and want just one place.

Be cautious together with your already established hql queries : when they used the hyperlink Product<->ProductList, they will not work any longer.

if you want to help keep the ManyToMany relation, you should think about : http://josephmarques.wordpress.com/2010/02/22/many-to-many-revisited/ (i have never attempted this solution)

public class Controller {

private static SessionFactory sf = HibernateUtil.getSessionFactory();

/**
* @param args
*/
public static void main(String[] args) {
// construct data
sf.getCurrentSession().beginTransaction();
Item i1 = new Item("i1");
Item i2 = new Item("i2");
Item i3 = new Item("i3");
Category c1 = new Category("c1");
sf.getCurrentSession().save(i1);
sf.getCurrentSession().save(i2);
sf.getCurrentSession().save(i3);
sf.getCurrentSession().save(c1);
c1.getItems().add(i1);
i1.getCategories().add(c1);
c1.getItems().add(i2);
i2.getCategories().add(c1);
sf.getCurrentSession().getTransaction().commit();

// get Category & i (i3)
sf.getCurrentSession().beginTransaction();
Category c = (Category) sf.getCurrentSession().get(Category.class, c1.getId());
Item i = (Item) sf.getCurrentSession().get(Item.class, i3.getId());

// proxys i & c have null Set
System.out.println("i : " + i.getName());
System.out.println("c : " + c.getName());

// here we have the IDs
long category_id = c.getId();
long item_id = i.getId();

sf.getCurrentSession().getTransaction().commit();

// add many to many data
sf.getCurrentSession().beginTransaction();

// here we can use pure SQL to add a line in CATEGORY_ITEM.
// with the known IDs
String ins = "insert into category_items (item_id,category_id,item_index) SELECT 4639, 100, MAX(item_index)+1 from category_items where category_id = 100 ;";

sf.getCurrentSession().getTransaction().commit();

}

}