华英杰 發表於 2025-6-25 17:32:00

springboot~hibernate实现外键表加载

<ul>
<li>https://www.baeldung.com/jpa-join-column</li>
<li>https://docs.oracle.com/javaee/6/api/javax/persistence/JoinColumn.html</li>
<li>https://stackoverflow.com/questions/37542208/what-is-joincolumn-and-how-it-is-used-in-hibernate</li>
</ul>
<p>在 JPA 中,通过外键自动获取关联对象的值通常使用关系映射注解(如 <code>@ManyToOne</code>)来实现。以下是针对 <code>am_application</code> 表的外键关联实现:</p>
<h3 id="1-首先创建被关联的实体-amsubscriber">1. 首先创建被关联的实体 <code>AmSubscriber</code></h3>
<pre><code class="language-java">import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;

@Getter
@Setter
@Entity
@Table(name = "am_subscriber")
public class AmSubscriber {
   
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "SUBSCRIBER_ID")
    private Integer subscriberId;
   
    @Column(name = "USER_ID", length = 64)
    private String userId;
   
    @Column(name = "TENANT_ID")
    private Integer tenantId;
   
    @Column(name = "EMAIL_ADDRESS", length = 256)
    private String emailAddress;
   
    @Column(name = "DATE_SUBSCRIBED")
    private LocalDateTime dateSubscribed;
   
    // 其他字段...
}
</code></pre>
<h3 id="2-在-amapplication-中添加关联关系">2. 在 <code>AmApplication</code> 中添加关联关系</h3>
<pre><code class="language-java">import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import java.time.LocalDateTime;

@Getter
@Setter
@Entity
@Table(name = "am_application",
       uniqueConstraints = {
         @UniqueConstraint(name = "uk_app_name_subscriber_org", columnNames = {"name", "subscriber", "organization"}),
         @UniqueConstraint(name = "uk_app_uuid", columnNames = "uuid")
       },
       indexes = {
         @Index(name = "idx_subscriber_id", columnList = "subscriber_id"),
         @Index(name = "idx_aa_at_cb", columnList = "applicationTier, createdBy")
       })
public class AmApplication {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "APPLICATION_ID")
    private Integer applicationId;

    @Column(name = "NAME", length = 100)
    private String name;

    // 关键:使用 @ManyToOne 建立外键关联
    @ManyToOne(fetch = FetchType.LAZY) // 推荐使用懒加载
    @JoinColumn(name = "SUBSCRIBER_ID", referencedColumnName = "SUBSCRIBER_ID")
    private AmSubscriber subscriber;

    // 其他字段保持不变...
    @Column(name = "APPLICATION_TIER", length = 50, columnDefinition = "varchar(50) default 'Unlimited'")
    private String applicationTier = "Unlimited";
   
    // ... (其他字段)
}
</code></pre>
<h3 id="关键注解说明">关键注解说明:</h3>
<ol>
<li>
<p><strong><code>@ManyToOne</code></strong>:</p>
<ul>
<li>表示多对一关系(多个应用对应一个订阅者)</li>
<li><code>fetch = FetchType.LAZY</code>:推荐使用懒加载,只在需要时加载关联对象</li>
</ul>
</li>
<li>
<p><strong><code>@JoinColumn</code></strong>:</p>
<ul>
<li><code>name = "SUBSCRIBER_ID"</code>:指定当前表中的外键列名</li>
<li><code>referencedColumnName = "SUBSCRIBER_ID"</code>:指定目标表的主键列名</li>
</ul>
</li>
</ol>
<h3 id="使用示例">使用示例:</h3>
<h4 id="保存新应用自动关联订阅者">保存新应用(自动关联订阅者)</h4>
<pre><code class="language-java">// 先获取订阅者
AmSubscriber subscriber = subscriberRepository.findById(123).orElseThrow();

// 创建新应用并关联订阅者
AmApplication app = new AmApplication();
app.setName("PaymentApp");
app.setSubscriber(subscriber); // 设置关联对象
app.setOrganization("finance");

// 保存时会自动处理外键关系
applicationRepository.save(app);
</code></pre>
<h4 id="查询应用并获取关联对象">查询应用并获取关联对象</h4>
<pre><code class="language-java">// 查询应用
AmApplication app = applicationRepository.findById(456).orElseThrow();

// 获取关联的订阅者信息(懒加载,首次访问时查询)
System.out.println("订阅者ID: " + app.getSubscriber().getSubscriberId());
System.out.println("订阅者邮箱: " + app.getSubscriber().getEmailAddress());
</code></pre>
<h4 id="jpql-查询自动关联">JPQL 查询(自动关联)</h4>
<pre><code class="language-java">@Query("SELECT a FROM AmApplication a " +
       "JOIN FETCH a.subscriber s " +// 使用JOIN FETCH避免N+1查询
       "WHERE a.organization = :org")
List&lt;AmApplication&gt; findByOrgWithSubscriber(@Param("org") String organization);
</code></pre>
<h4 id="自定义查询方法">自定义查询方法</h4>
<pre><code class="language-java">public interface AmApplicationRepository extends JpaRepository&lt;AmApplication, Integer&gt; {
   
    // 通过订阅者邮箱查找应用
    List&lt;AmApplication&gt; findBySubscriberEmailAddress(String email);
   
    // 通过订阅者ID查找应用
    List&lt;AmApplication&gt; findBySubscriberSubscriberId(Integer subscriberId);
}
</code></pre>
<h3 id="处理双向关系可选">处理双向关系(可选)</h3>
<p>如果需要在订阅者端反向访问应用列表,可以添加双向映射:</p>
<pre><code class="language-java">// 在 AmSubscriber 类中添加
@OneToMany(mappedBy = "subscriber", cascade = CascadeType.ALL)
private List&lt;AmApplication&gt; applications = new ArrayList&lt;&gt;();

// 在 AmApplication 中保持关联时同步更新
public void setSubscriber(AmSubscriber subscriber) {
    this.subscriber = subscriber;
    if (subscriber != null &amp;&amp; !subscriber.getApplications().contains(this)) {
      subscriber.getApplications().add(this);
    }
}
</code></pre>
<h3 id="保留两个外键字段">保留两个外键字段</h3>
<ul>
<li>保留subscriber_id字段</li>
<li>保留subscriber对象字段</li>
<li>insertable = false, updatable = false属性是关键</li>
</ul>
<pre><code>public class AmApplication {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "APPLICATION_ID")
    private Integer applicationId;

    @Column(name = "NAME", length = 100)
    private String name;

    // 关键:使用 @ManyToOne 建立外键关联
    @ManyToOne(fetch = FetchType.LAZY) // 推荐使用懒加载
    @JoinColumn(name = "当前表字段SUBSCRIBER_ID", referencedColumnName = "父表字段SUBSCRIBER_ID", insertable = false, updatable = false)
    private AmSubscriber subscriber;
   
    Column(name="SUBSCRIBER_ID")
    private String SUBSCRIBER_ID;
</code></pre>
<h3 id="注意事项">注意事项:</h3>
<ol>
<li>
<p><strong>避免循环引用</strong>:</p>
<ul>
<li>使用 <code>@JsonIgnore</code> 防止 JSON 序列化无限循环</li>
</ul>
<pre><code class="language-java">@OneToMany(mappedBy = "subscriber")
@JsonIgnore
private List&lt;AmApplication&gt; applications;
</code></pre>
</li>
<li>
<p><strong>N+1 查询问题</strong>:</p>
<ul>
<li>使用 <code>JOIN FETCH</code> 或实体图解决</li>
</ul>
<pre><code class="language-java">@EntityGraph(attributePaths = "subscriber")
List&lt;AmApplication&gt; findByOrganization(String org);
</code></pre>
</li>
<li>
<p><strong>更新操作</strong>:</p>
<ul>
<li>更新关联对象时,JPA 会自动同步外键值</li>
</ul>
<pre><code class="language-java">// 更改应用的订阅者
AmSubscriber newSub = subscriberRepository.findById(789).orElseThrow();
app.setSubscriber(newSub);
applicationRepository.save(app); // 自动更新外键
</code></pre>
</li>
<li>
<p><strong>删除策略</strong>:</p>
<ul>
<li>默认 <code>@ManyToOne</code> 使用 <code>FetchType.EAGER</code></li>
<li>推荐显式设置 <code>fetch = FetchType.LAZY</code></li>
<li>删除订阅者时需先处理关联应用(或使用级联删除)</li>
</ul>
</li>
</ol>
<p>这样配置后,JPA 会自动处理外键关系,在访问 <code>AmApplication</code> 的 <code>subscriber</code> 属性时,会通过外键 <code>SUBSCRIBER_ID</code> 自动加载关联的 <code>AmSubscriber</code> 对象。</p>


</div>
<div id="MySignature" role="contentinfo">
    <p></p>
<div class="navgood">
<p>作者:仓储大叔,张占岭,<br>
荣誉:微软MVP<br>QQ:853066980</p>

<p><strong>支付宝扫一扫,为大叔打赏!</strong>
<br><img src="https://images.cnblogs.com/cnblogs_com/lori/237884/o_IMG_7144.JPG"></p>
</div><br><br>
来源:https://www.cnblogs.com/lori/p/18948489
頁: [1]
查看完整版本: springboot~hibernate实现外键表加载