1 /*
2 * Hibernate, Relational Persistence for Idiomatic Java
3 *
4 * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
5 * indicated by the @author tags or express copyright attribution
6 * statements applied by the authors. All third-party contributions are
7 * distributed under license by Red Hat Middleware LLC.
8 *
9 * This copyrighted material is made available to anyone wishing to use, modify,
10 * copy, or redistribute it subject to the terms and conditions of the GNU
11 * Lesser General Public License, as published by the Free Software Foundation.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
16 * for more details.
17 *
18 * You should have received a copy of the GNU Lesser General Public License
19 * along with this distribution; if not, write to:
20 * Free Software Foundation, Inc.
21 * 51 Franklin Street, Fifth Floor
22 * Boston, MA 02110-1301 USA
23 *
24 */
25 package org.hibernate.type;
26
27 import java.io.Serializable;
28 import java.sql.PreparedStatement;
29 import java.sql.ResultSet;
30 import java.sql.SQLException;
31 import java.util.Arrays;
32
33 import org.hibernate.AssertionFailure;
34 import org.hibernate.HibernateException;
35 import org.hibernate.MappingException;
36 import org.hibernate.engine.EntityKey;
37 import org.hibernate.engine.ForeignKeys;
38 import org.hibernate.engine.Mapping;
39 import org.hibernate.engine.SessionImplementor;
40 import org.hibernate.persister.entity.EntityPersister;
41
42 /**
43 * A many-to-one association to an entity.
44 *
45 * @author Gavin King
46 */
47 public class ManyToOneType extends EntityType {
48
49 private final boolean ignoreNotFound;
50
51 public ManyToOneType(String className) {
52 this( className, false );
53 }
54
55 public ManyToOneType(String className, boolean lazy) {
56 super( className, null, !lazy, true, false );
57 this.ignoreNotFound = false;
58 }
59
60 public ManyToOneType(
61 String entityName,
62 String uniqueKeyPropertyName,
63 boolean lazy,
64 boolean unwrapProxy,
65 boolean isEmbeddedInXML,
66 boolean ignoreNotFound) {
67 super( entityName, uniqueKeyPropertyName, !lazy, isEmbeddedInXML, unwrapProxy );
68 this.ignoreNotFound = ignoreNotFound;
69 }
70
71 protected boolean isNullable() {
72 return ignoreNotFound;
73 }
74
75 public boolean isAlwaysDirtyChecked() {
76 // If we have <tt>not-found="ignore"</tt> association mapped to a
77 // formula, we always need to dirty check it, so we can update the
78 // second-level cache
79 return ignoreNotFound;
80 }
81
82 public boolean isOneToOne() {
83 return false;
84 }
85
86 public int getColumnSpan(Mapping mapping) throws MappingException {
87 // our column span is the number of columns in the PK
88 return getIdentifierOrUniqueKeyType( mapping ).getColumnSpan( mapping );
89 }
90
91 public int[] sqlTypes(Mapping mapping) throws MappingException {
92 return getIdentifierOrUniqueKeyType( mapping ).sqlTypes( mapping );
93 }
94
95 public void nullSafeSet(
96 PreparedStatement st,
97 Object value,
98 int index,
99 boolean[] settable,
100 SessionImplementor session) throws HibernateException, SQLException {
101 getIdentifierOrUniqueKeyType( session.getFactory() )
102 .nullSafeSet( st, getIdentifier( value, session ), index, settable, session );
103 }
104
105 public void nullSafeSet(
106 PreparedStatement st,
107 Object value,
108 int index,
109 SessionImplementor session) throws HibernateException, SQLException {
110 getIdentifierOrUniqueKeyType( session.getFactory() )
111 .nullSafeSet( st, getIdentifier( value, session ), index, session );
112 }
113
114 public ForeignKeyDirection getForeignKeyDirection() {
115 return ForeignKeyDirection.FOREIGN_KEY_FROM_PARENT;
116 }
117
118 public Object hydrate(
119 ResultSet rs,
120 String[] names,
121 SessionImplementor session,
122 Object owner) throws HibernateException, SQLException {
123 // return the (fully resolved) identifier value, but do not resolve
124 // to the actual referenced entity instance
125 // NOTE: the owner of the association is not really the owner of the id!
126 Serializable id = (Serializable) getIdentifierOrUniqueKeyType( session.getFactory() )
127 .nullSafeGet( rs, names, session, null );
128 scheduleBatchLoadIfNeeded( id, session );
129 return id;
130 }
131
132 /**
133 * Register the entity as batch loadable, if enabled
134 */
135 private void scheduleBatchLoadIfNeeded(
136 Serializable id,
137 SessionImplementor session) throws MappingException {
138 //cannot batch fetch by unique key (property-ref associations)
139 if ( uniqueKeyPropertyName == null && id != null ) {
140 EntityPersister persister = session.getFactory().getEntityPersister( getAssociatedEntityName() );
141 EntityKey entityKey = new EntityKey( id, persister, session.getEntityMode() );
142 if ( !session.getPersistenceContext().containsEntity( entityKey ) ) {
143 session.getPersistenceContext()
144 .getBatchFetchQueue()
145 .addBatchLoadableEntityKey( entityKey );
146 }
147 }
148 }
149
150 public boolean useLHSPrimaryKey() {
151 return false;
152 }
153
154 public boolean isModified(
155 Object old,
156 Object current,
157 boolean[] checkable,
158 SessionImplementor session) throws HibernateException {
159 if ( current == null ) {
160 return old!=null;
161 }
162 if ( old == null ) {
163 // we already know current is not null...
164 return true;
165 }
166 // the ids are fully resolved, so compare them with isDirty(), not isModified()
167 return getIdentifierOrUniqueKeyType( session.getFactory() )
168 .isDirty( old, getIdentifier( current, session ), session );
169 }
170
171 public Serializable disassemble(
172 Object value,
173 SessionImplementor session,
174 Object owner) throws HibernateException {
175
176 if ( isNotEmbedded( session ) ) {
177 return getIdentifierType( session ).disassemble( value, session, owner );
178 }
179
180 if ( value == null ) {
181 return null;
182 }
183 else {
184 // cache the actual id of the object, not the value of the
185 // property-ref, which might not be initialized
186 Object id = ForeignKeys.getEntityIdentifierIfNotUnsaved(
187 getAssociatedEntityName(),
188 value,
189 session
190 );
191 if ( id == null ) {
192 throw new AssertionFailure(
193 "cannot cache a reference to an object with a null id: " +
194 getAssociatedEntityName()
195 );
196 }
197 return getIdentifierType( session ).disassemble( id, session, owner );
198 }
199 }
200
201 public Object assemble(
202 Serializable oid,
203 SessionImplementor session,
204 Object owner) throws HibernateException {
205
206 //TODO: currently broken for unique-key references (does not detect
207 // change to unique key property of the associated object)
208
209 Serializable id = assembleId( oid, session );
210
211 if ( isNotEmbedded( session ) ) {
212 return id;
213 }
214
215 if ( id == null ) {
216 return null;
217 }
218 else {
219 return resolveIdentifier( id, session );
220 }
221 }
222
223 private Serializable assembleId(Serializable oid, SessionImplementor session) {
224 //the owner of the association is not the owner of the id
225 return ( Serializable ) getIdentifierType( session ).assemble( oid, session, null );
226 }
227
228 public void beforeAssemble(Serializable oid, SessionImplementor session) {
229 scheduleBatchLoadIfNeeded( assembleId( oid, session ), session );
230 }
231
232 public boolean[] toColumnNullness(Object value, Mapping mapping) {
233 boolean[] result = new boolean[ getColumnSpan( mapping ) ];
234 if ( value != null ) {
235 Arrays.fill( result, true );
236 }
237 return result;
238 }
239
240 public boolean isDirty(
241 Object old,
242 Object current,
243 SessionImplementor session) throws HibernateException {
244 if ( isSame( old, current, session.getEntityMode() ) ) {
245 return false;
246 }
247 Object oldid = getIdentifier( old, session );
248 Object newid = getIdentifier( current, session );
249 return getIdentifierType( session ).isDirty( oldid, newid, session );
250 }
251
252 public boolean isDirty(
253 Object old,
254 Object current,
255 boolean[] checkable,
256 SessionImplementor session) throws HibernateException {
257 if ( isAlwaysDirtyChecked() ) {
258 return isDirty( old, current, session );
259 }
260 else {
261 if ( isSame( old, current, session.getEntityMode() ) ) {
262 return false;
263 }
264 Object oldid = getIdentifier( old, session );
265 Object newid = getIdentifier( current, session );
266 return getIdentifierType( session ).isDirty( oldid, newid, checkable, session );
267 }
268
269 }
270
271 }