<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    gembin

    OSGi, Eclipse Equinox, ECF, Virgo, Gemini, Apache Felix, Karaf, Aires, Camel, Eclipse RCP

    HBase, Hadoop, ZooKeeper, Cassandra

    Flex4, AS3, Swiz framework, GraniteDS, BlazeDS etc.

    There is nothing that software can't fix. Unfortunately, there is also nothing that software can't completely fuck up. That gap is called talent.

    About Me

     

    JPA 學習

    Defining Your Object Model with JPA


    In the perfect world, your object model would map seamlessly to your database schema. Most organizations however, have database naming standards, requirements for how relationships are modeled and columns that all tables must have.

    The following example will show how to use JPA in a real-world scenario. You’ve been asked to quickly mock up a simple blog application; here are your constraints:

    • All database table names are uppercase, words separated by an underscore (“_”)
    • All table columns must start with the table name, or its initials
    • All mutable tables need to track who created and updated a row and when the insert and update occurred
    • Object relationships need to be managed intelligently
    • You should use as few trips as possible to the database to make sure we can handle current and future load

    The application should include the following objects:

    • User
    • Blog entry
    • Comments
    • The comments need to support nesting, so a user can reply to another’s comment, there is no limit to the nesting
    • You need to list all users who start comment threads, comments without responses, etc.

    Here is one possible object model:

    Figure 1: Blog Object Model

    ModelBase is an abstract superclass of three entities: BlogPost, Comment, and User. It also defines fields for who created and modified each entity and when the changes occurred. The nested comment requirement is taken care of by Comment’s parent and child relationships.

    The database schema for the object model is:

    Figure 2: Blog Entity Relationship Diagram

    The database tables can be seen here:

    The root object of the class hierarchy is ModeBase. The @Column annotation maps each field in ModelBase to a database table column. The problem is, ModelBase does not follow the database naming conventions listed above. This will be fixed with the concrete class definitions a little later. Here is ModelBase:

     1 import javax.persistence.*;
    2
    3 @MappedSuperclass
    4 @EntityListeners({ModelListener.class})
    5 public abstract class ModelBase {
    6
    7 @Id
    8 @GeneratedValue(strategy = GenerationType.IDENTITY)
    9 @Column(name="id")
    10 private Long id;
    11
    12 @Version
    13 @Column(name="version")
    14 private Integer version;
    15
    16 @ManyToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
    17 @JoinColumn(name="created_by_user_id")
    18 private User createdByUser;
    19
    20 @Temporal(value = TemporalType.TIMESTAMP)
    21 @Column(name = "date_created", nullable = false)
    22 private Date dateCreated;
    23
    24 @ManyToOne(fetch=FetchType.LAZY)
    25 @JoinColumn(name="updated_by_user_id")
    26 private User updatedByUser;
    27
    28 @Temporal(value = TemporalType.TIMESTAMP)
    29 @Column(name = "date_updated", nullable = false)
    30 private Date dateUpdated;
    31
    32 // methods removed for readability
    33 }

    ModelBase uses the @MappedSuperclass annotation to tell the JPA persistence provide that this object is not an entity but it’s fields will be included in each entities table (for the entities that subclass ModelBase). You can use a mapped superclass to define all common fields. In this case it defines a field for optimist locking, version, primary key, id and fields for date created and updated. The second annotation, @EntityListener defines a class to be called by the JPA persistence provider at various lifecycle events. ModelListener, sets the user who created and modified each entity and when it was created and updated. Here is ModelListener:

     1 public class ModelListener {
    2
    3 @PrePersist
    4 public void setDatesAndUser(ModelBase modelBase) {
    5
    6 // set createdBy and updatedBy User information
    7 User currentUser = UserUtil.getCurrentUser();
    8
    9 // check to see if modelBase and currentUser are
    10 // the same, if so, make currentUser modelBase.
    11 if (modelBase.equals(currentUser)) {
    12 currentUser = (User) modelBase;
    13 }
    14
    15 if (currentUser != null) {
    16 if (modelBase.getCreatedByUser() == null) {
    17 modelBase.setCreatedByUser(currentUser);
    18 }
    19 modelBase.setUpdatedByUser(currentUser);
    20 }
    21
    22 // set dateCreated and dateUpdated fields
    23 Date now = new Date();
    24 if (modelBase.getDateCreated() == null) {
    25 modelBase.setDateCreated(now);
    26 }
    27 modelBase.setDateUpdated(now);
    28 }
    29 }

    ModelListener has only one method, setDatesAndUser(ModelBase modelBase). This method is tied to the pre-persist entity lifecycle event and will always be called just before an entity is persisted to the database. This gives us a convenient way to set when and by whom the object is created and updated.

    JPA requires that entity listeners be stateless and therefore we need a way to establish who the current user of our system is so we can set the createdByUser and updatedByUser fields. The UserUtil class uses a ThreadLocal to store the current user and provides an easy way for clients to establish the “current” user of the system.

     1 public class UserUtil {
    2
    3 private static ThreadLocal<User> currentUser =
    4 new ThreadLocal<User>();
    5
    6 public static void setCurrentUser(User user) {
    7 currentUser.set(user);
    8 }
    9
    10 public static User getCurrentUser() {
    11 return currentUser.get();
    12 }
    13 }

    If you want to turn off any inherited entity listeners for a particular entity, you can use @ExcludeSuperclassListeners. This annotation does not have any elements, you add it to your entity like this:

    1 @ExcludeDefaultListeners
    2 public class Comment extends ModelBase implements Serializable

    Now, Comment will not be passed to ModelListener when it’s persisted.

    There are a couple of drawbacks to using entity listeners, most notably the lack of any lifecycle methods for the listener itself, it must be stateless, and it must have a public no-arg constructor. If you can live with these restrictions entity listeners are a good way to hook into the JPA entity lifecycle.

    JPA also supports callback methods, these are methods in your object model class itself and must have the following method signature: void methodName(). Use the entity lifecycle event annotations (e.g. @PrePersist or @PostPersist) to indicate which event the callback method participates in. Here is an example of a callback method (continuing with the example of Comment turning off it’s entity listeners):

     1 @ExcludeDefaultListeners
    2 public class Comment extends ModelBase implements Serializable {
    3
    4 // code removed for readability
    5
    6 @PrePersist
    7 public void setDates() {
    8 Date now = new Date();
    9 if (getDateCreated() == null) {
    10 setDateCreated(now);
    11 }
    12 setDateUpdated(now);
    13 }
    14 }

    By default, an entities table name is the same as the entity itself. In the case of BlogPost, the default table name would be BlogPost. To explicitly set the table name an entity is mapped to, use the @Table annotation. To change the column mappings defined in ModelBase, use @AttributeOverride. If you need to override more than one field, use the @AttributeOverrides (plural) annotation. Here is how you change the mappings:

    1 @Entity
    2 @Table(name = "BLOG_POST")
    3 @AttributeOverrides( { @AttributeOverride(name = "id", column = @Column(name = "BP_ID")),
    4 @AttributeOverride(name="version", column=@Column(name="BP_VERSION")),
    5 @AttributeOverride(name="dateCreated", column=@Column(name="BP_DATE_CREATED")),
    6 @AttributeOverride(name="dateUpdated", column=@Column(name="BP_DATE_UPDATED"))
    7 })
    8 public class BlogPost extends ModelBase implements Serializable {

    The two remaining fields to remap from ModelBase are not attributes but associations, so you need to use a different set of annotations, @AssociationOverrides (plural) and @AssociationOverride. Here is how you rename the createdByUser and updatedByUser foreign key columns in the BlogPost entity:

    1 @AssociationOverrides( {
    2 @AssociationOverride(name="createdByUser",
    3 joinColumns=@JoinColumn(name="BP_CREATED_BY_USER_ID")),
    4
    5 @AssociationOverride(name="updatedByUser",
    6 joinColumns=@JoinColumn(name="BP_UPDATED_BY_USER_ID"))
    7 })

    As you can see, the @AssociationOverride annotation is a little be more complex than @AttributeOverride because it has a nested annotation, @JoinColumn.

    As you can see in Figure 1, there are three more fields to be defined in BlogPost. Here is their definition:

    1     @Lob
    2 @Column(name = "BP_CONTENT")
    3 private String content;
    4
    5 @Column(name="BP_TITLE")
    6 private String title;
    7
    8 @Column(name = "BP_EDIT_COUNT")
    9 private Integer editCount;

    The @Lob annotation tells the persistence provider that content is bound to a large object type column. The @Column annotation defines the column name the content field is mapped to. Use the @Column annotation to map an entities field to a specific column name.

    One last thing to look at is BlogPost’s relationship to Comment. Here is how the comments field is defined:

    1     @OneToMany(cascade={CascadeType.PERSIST,CascadeType.MERGE},
    2 fetch=FetchType.LAZY)
    3 @Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
    4 private List<Comment> comments = new ArrayList<Comment>();

    The OneToMany annotation tells the persistence provider this is an association and not an attribute. The cascade element tells the persistence provider to persist or merge any comments associated with this instance when blog post is persisted or merged. Unfortunately, JPA does not provide a cascade type for managing orphaned deletes. Fortunately, the Hibernate team has created a set of Hibernate extension annotations to make managing relationships (and every other aspect of an entity) easier. The delete orphan cascade annotation lets Hibernate know it can delete a row from the comment table if a comment is removed from the comments list. Without this annotation, you would have to manually delete the removed comment.

    There are two ways for the persistence provider to map a one-to-many relationship in the database. One is to use a join table (a unidirectional relationship) and the other is to have a foreign key column in the many side of the relationship (a bidirectional relationship). Given the BlogPost to Comment relationship, if you just defined the blog post to comment relationship as shown above, JPA should expect the following tables in the database:

    Figure 3: Blog Post to Comment join table

    You eliminate the join table, BlogPost_Comment, by defining a bidirectional relationship. To tell JPA this is a bidirectional relationship, you need to add the mappedBy element to the @OneToMany annotation like this:

    1     @OneToMany(cascade={CascadeType.PERSIST,CascadeType.MERGE},
    2 fetch=FetchType.LAZY, mappedBy="blogPost")
    3 @Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
    4 private List<Comment> comments = new ArrayList<Comment>();

    The mappedBy element indicates the owning-side of the relationship, or the field in Comment that will hold a reference to BlogPost. Here is the comment side of the relationship:

    1     @ManyToOne
    2 @JoinColumn(name="COMMENT_BLOG_POST_ID")
    3 private BlogPost blogPost;

    By defining the post to comments relationship this way we eliminate the need for a join table.

    The Comment entity has the same attribute and association overrides as BlogPost (mapping to different column names of course), however it has an interesting relationship to itself. Comments support nested comments, so a comment can contain a list of responses to itself. To create this relationship, Comment needs two fields, one for the collection of comments and another to represent the parent comment. Here is how the fields are defined:

    1     @ManyToOne
    2 @JoinColumn(name="COMMENT_PARENT")
    3 private Comment parent;
    4
    5 @OneToMany(mappedBy="parent",
    6 cascade={CascadeType.MERGE, CascadeType.PERSIST})
    7 @OrderBy("dateCreated DESC")
    8 private List<Comment> children = new ArrayList<Comment>();

    These relationships are the same as any other one-to-many bidirectional relationship between BlogPost and Comment; the only difference is both sides of the relationship are in the same class. The parent field holds the primary key of the parent comment.

    Now that you have the fields defined, you need to add methods to manage the relationship. Both sides of the relationship need to be set in order for the Comment entity to behave properly. To ensure the relationship is set up correctly the java bean property methods for children need to be slightly modified. Since the JPA annotations used in the Comment entity are field based, the JPA persistence provider does not use property accessor methods to set its state. This means we do not need the public void setChildren(List<Comment> children) method for the persistence provider. In addition, by removing this method clients of our model cannot directly set the collection. Next is the public List<Comment> getChildren() method; like setChildren() this method allows clients to directly modify a comments children. To fix this, make getChildren() return an immutable list, like this:

    1     public List<Comment> getChildren() {
    2 return Collections.unmodifiableList(children);
    3 }

    The final step is to define methods to add and remove child comments. Here is the method for adding child comments:

     1     public void addChildComment(Comment comment) {
    2 if (comment == null) {
    3 throw new IllegalArgumentException("child comment is null!");
    4 }
    5
    6 // check to see if comment has a parent
    7 if (comment.getParent() != null) {
    8 // if there is a parent check to see if it's already
    9 // associated to this comment
    10 if (comment.getParent().equals(this)) {
    11 // if this instance is already the parent, we can just return
    12 return;
    13 }
    14 else {
    15 // disconnect post from it's current relationship
    16 comment.getParent().children.remove(this);
    17 }
    18 }
    19
    20 // make this instance the new parent
    21 comment.setParent(this);
    22 children.add(comment);
    23 }

    If you want to add a remove comment method, it might look like this:

     1     public void removeChildComment(Comment comment) {
    2 if (comment == null) {
    3 throw new IllegalArgumentException("child comment is null!");
    4 }
    5
    6 // make sure we are the parent before we break the relationship
    7 if (comment.parent != null && comment.getParent().equals(this)) {
    8 comment.setParent(null);
    9 children.remove(comment);
    10 }
    11 else {
    12 throw new IllegalArgumentException(
    13 "child comment not associated with this instance");
    14 }
    15 }

    Not only does this method remove a comment from its parents children collection, it makes sure the parent of comment is this instance. If comment were not associated with this instance, removing it from children would have no affect. However, setting its parent to null would leave a dangling comment in the database.

    The last two methods to deal with are getParent() and setParent() . Here is their definition:

    1     public Comment getParent() {
    2 return parent;
    3 }
    4
    5 private void setParent(Comment parent) {
    6 this.parent = parent;
    7 }

    The addChildComment() method uses setParent() , however; clients of the object model should not be able to change a comments parent. The easiest way to ensure this is to make setParent() private. getParent() is fine the way it is. The pattern used for Comments parent/child relationship can be applied to any bidirectional association in your object model.

    Deciding where to put the relationship management methods in a bidirectional relationship is rather arbitrary since the purpose of the methods is to ensure the relationships are established correctly. With one-to-many bidirectional relationships, I tend to put the management methods on the “one” side. In the case of Comment this isn’t obvious, but in the BlogPost to Comment relationship I placed the relationship methods in BlogPost. This seems more natural; you add comments to a blog post, not the other way around. If you have a many-to-many relationship it really doesn’t matter which side has the methods so pick one and restrict the other side.

    JPA provides methods for fetching entities by their primary key but it might be a good idea to provide some queries to fetch blog posts and comments by User, count the number of comments and blog posts a user has made, determine which comments do not have child comments, and find all root comments (those without a parent).

    JPA provides three different types of queries, dynamic queries, static or named queries, and native queries. Dynamic and static queries use the Java Persistence Query Language and native queries use SQL. A dynamic query is one that is processed at runtime, meaning it is parsed and SQL generated every time it’s created. Static queries are processed when the persistence provider loads your object model. This means static queries are parsed once and reused every time you run the query.

    To declare a named or static query, you use the @NamedQuery annotation. If you have more than one named query, you need to use the @NamedQueries annotation. Both annotations can be placed on an entity or mapped superclass and are declared at the class or type level. Query names are global, that is, they are not bound to any entity. As a result, you should use some sort of naming convention. One approach is to prefix every query name with the entity name it’s associated with.

    Here are the named queries used in Comment and BlogPost:

     1 @NamedQueries({
    2 // select comments that do not have parent comments
    3 @NamedQuery(name = "comment.rootComments",
    4 query = "SELECT c FROM Comment c WHERE c.parent IS NULL"),
    5
    6 // select comments made by a User
    7 @NamedQuery(name = "comment.userComments",
    8 query = "SELECT c FROM Comment c WHERE c.createdByUser = :user"),
    9
    10 // count the number of comments made by a user
    11 @NamedQuery(name = "comment.userCount",
    12 query = "SELECT COUNT(c) FROM Comment c WHERE c.createdByUser = :user"),
    13
    14 // select the comments a user made without responses
    15 @NamedQuery(name = "comment.noChildren",
    16 query = "SELECT c FROM Comment c WHERE c.children IS EMPTY AND c.parent IS NULL AND c.createdByUser = ?1")
    17 })
    18 public class Comment extends ModelBase implements Serializable{ ... }
    19
    20
    21 @NamedQueries( {
    22 @NamedQuery(name = "blogPost.createdByUser",
    23 query = "SELECT p FROM BlogPost p WHERE p.createdByUser = ?1"),
    24
    25 // determine the number of posts a User has made
    26 @NamedQuery(name = "blogPost.postCount",
    27 query = "SELECT COUNT(p) FROM BlogPost p WHERE p.createdByUser = ?1"),
    28
    29 // fetch a blog post and eagerly fetch its comments
    30 @NamedQuery(name = "blogPost.createdByUserComments",
    31 query = "SELECT p FROM BlogPost p JOIN FETCH p.comments as c WHERE p.createdByUser = ?1")
    32 })
    33 public class BlogPost extends ModelBase implements Serializable { ... }

    Each of the above queries uses either positional or named parameters; positional parameters use the “?1” syntax and named parameters use the “:name” syntax. Here is how you would create and execute a static query which uses named parameters:

    1 // assume entityManager exists
    2 User user = UserUtil.getCurrentUser();
    3
    4 // Here is the definition of comment.userComments for reference
    5 // SELECT c FROM Comment c WHERE c.createdByUser = :user
    6
    7 Query q = entityManager.createNamedQuery("comment.userComments");
    8 q.setParameter("user", user);
    9 List results = q.getResultList();

    Another query using positional parameters (note: the count function returns a Long):

    1 // assume entityManager exists
    2 User user = UserUtil.getCurrenstUser();
    3
    4 // Here is the definition of blogPost.postCount for reference
    5 // SELECT COUNT(p) FROM BlogPost p WHERE p.createdByUser = ?1
    6
    7 Query q = entityManager.createNamedQuery("blogPost.postCount");
    8 q.setParameter(1, user);
    9 Long result = (Long) q.getSingleResult();

    The JPQL has many built in functions and expressions to assist you in working with your object model. If you have not checked out JPQL before, you might be surprised by how powerful it is.

    The last query to look at is blogPost.createdByUserComments. This query uses the fetch join operator to eagerly fetch a blog posts’ comments. The BlogPost to Comment association is defined as being LAZY (here is the relationship again):

    1     @OneToMany(cascade={CascadeType.PERSIST,CascadeType.MERGE},
    2 fetch=FetchType.LAZY, mappedBy="blogPost")
    3 @Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
    4 private List<Comment> comments = new ArrayList<Comment>();

    Making the relationship LAZY (by setting the fetch element of the @OneToMany annotation to FetchType.LAZY) enables your application to fetch a blog post, say when a user wants to edit the post, without also fetching all the comments. When you want to fetch the blog post and its comments, you can use the named query, blogPost.createdByUserComments. Sometimes you will want to make a relationship eager, by setting the fetch element of the @OneToMany annotation to FetchType.EAGER, so when you fetch entity “A” the persistence provider will also fetch entity “B”. Having one fetch to get back several objects is more efficient than multiple trips to the database but you will need to decide the correct semantics for each collection in your object model.

    If you haven’t looked at JPA before, I hope this short example will encourage you to give it a try. Hibernate’s latest release has excellent JPA support and provides an extensive set of JPA extension annotations.

    Get the Source Code here.

    Biography

    Chris Maki is a Principal Software Engineer at Overstock.com. Before joining Overstock.com, Chris was an independent consultant specializing in server-side Java development. He has been designing and building server-side systems for 15 years in C++, Objective-C, and Java.

    Chris spent many years working as an enterprise architect specializing in large-scale Java system design and development. In addition to being a Java evangelist, he is an avid proponent of Agile Software Development.

    Chris is the President of the Utah Java User Group and a member of the WebBeans (JSR-299) and JPA 2.0 (JSR-317) expert groups.

    When Chris isn?t playing with his computer, you can find him hanging out with his wonderful wife of 12 years, Nicole, and their three boys, Trenton, Avery, and Brayden. You can find Chris on his blog at http://www.jroller.com/page/cmaki.

    posted on 2008-04-30 19:19 gembin 閱讀(2527) 評論(1)  編輯  收藏 所屬分類: JavaEE

    評論

    # re: JPA 學習 2010-06-18 11:29 lacewigs

    great site. Thanks for sharing with us!!!  回復  更多評論   

    導航

    統計

    常用鏈接

    留言簿(6)

    隨筆分類(440)

    隨筆檔案(378)

    文章檔案(6)

    新聞檔案(1)

    相冊

    收藏夾(9)

    Adobe

    Android

    AS3

    Blog-Links

    Build

    Design Pattern

    Eclipse

    Favorite Links

    Flickr

    Game Dev

    HBase

    Identity Management

    IT resources

    JEE

    Language

    OpenID

    OSGi

    SOA

    Version Control

    最新隨筆

    搜索

    積分與排名

    最新評論

    閱讀排行榜

    評論排行榜

    free counters
    主站蜘蛛池模板: 久久精品国产亚洲av水果派| 亚洲成A人片在线观看中文| 亚洲国产精品无码中文字| 香蕉国产在线观看免费| 国产hs免费高清在线观看| 亚洲色大成网站www永久网站| 成年人视频免费在线观看| 亚洲国产理论片在线播放| 国产无人区码卡二卡三卡免费| 亚洲an日韩专区在线| 最近中文字幕mv免费高清视频7| 亚洲一区二区三区深夜天堂| 男女做羞羞的事视频免费观看无遮挡| 亚洲国产精品成人精品软件| 亚洲成在人线aⅴ免费毛片| 亚洲精品成a人在线观看☆ | 18女人腿打开无遮掩免费| 亚洲综合一区二区国产精品| 曰曰鲁夜夜免费播放视频| 亚洲欧美aⅴ在线资源| 又大又硬又爽免费视频| 中文字幕免费在线播放| 亚洲va在线va天堂va888www| 午夜国产精品免费观看| 亚洲精品无码永久在线观看男男 | 日本免费xxxx色视频| 亚洲精品蜜夜内射| 国产美女亚洲精品久久久综合| 美女视频黄的免费视频网页 | 亚洲日韩国产一区二区三区在线 | 国产无遮挡吃胸膜奶免费看视频| 九九免费久久这里有精品23| 亚洲AV无码国产精品色午友在线| 无码av免费毛片一区二区| 黄网站色成年片大免费高清| 久久国产亚洲电影天堂| 免费无码黄网站在线观看| 岛国精品一区免费视频在线观看| 亚洲成aⅴ人片在线观| 亚洲区小说区图片区| 黄页网站在线看免费|