欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JPA学习笔记

程序员文章站 2022-04-22 08:05:23
...

JPA

一、基于 Hibernate 数据库持久化操作及 JPA 初始配置

需要导入的基础包

<denpendency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13</version>
    <scope>test</scope>
</denpendency>
<denpendecy>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-entitymanager</artifactId>
    <version>5.4.32.Final</version>
</denpendecy>
<!-- lombok,演示仅用作生成setter&getter,可以手动生成 -->
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.18.22</version>
</dependency>

声明实体的例子:

@Data
@Entity
@Table(name = "table_name")
public class Object {
    /**
     * @Id:用于声明主键
     * @GeneratedValue:配置主键的生成策略
     *     strategy
     *         GenerationType.IDENTITY : 自增,MySQL
     *             * 底层数据库必须支持自动增长(对id自增)
     *         GenerationType.SEQUENCE : 序列,Oracle
     *             * 底层数据库必须支持序列
     *         GenerationType.TABLE : jpa提供的一种机制,通过一张数据库表的形式帮助我们完成主键自增
     *         GenerationType.AUTO : 由程序自动地帮我们选择主键生成策略
     */
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private id;
    
    @Column(name = "other_properties")
    private String otherProperties;
    
    @Transient
    private String unsavedProperties;
        
    ...
        
        
}

@Data:给实体类加get/set/toString/EqualsAndHashCode方法,是lombok的注解

@Transient:如果不想将该属性保存到数据库,可以使用这个注释

resources/hibernate.cfg.xml相关配置:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
    <session-factory>
        <!-- 配置数据库连接信息 -->
        <property name="connection.driver_class">com.mysql.jdbc.Driver</property>
        <property name="connection.url">jdbc:mysql://localhost:3306/springdata_jpa?table?characterEncoding=UTF-8</property>
        <property name="connection.username">root</property>
        <property name="connection.password">123456</ property>
        
        <!-- 是否在日志中记录sql,默认为false -->
        <property name="show_sql">true</property>
        <!-- 是否格式化sql,默认为false -->
        <property name="format_sql">update</property>
        <!-- 配置方言:选择数据库类型 -->
        <property name="dialect">org.hibernate.dialect.MYSQL5InnoDBDiaclect</property>
        
        <!-- 映射 -->
        <mapping class="Object"></mapping>
	</session-factory>    
</hibernate-configuration>

数据访问层一个简单的例子:

private SessionFactory sf;

@Before
public void init(){
    StandardServiceRegistry registry = new StandardServiceRegistryBuilder().configure("/hibernate.cfg.xml").build;
    
    sf = new MetaDataSources(registry).buildMetadata().buildSessionFactory();   
}

@Test
public void Test(){
    try(Session session = sf.openSession()){
		Transcation transcation = session.beginTransaction;
        Object object = new Object();
        object.setOtherProperties("其他属性");
        
        session.save(object);
        transcation.commit();
    }
}

插入和更新使用的语句都是save()(或saveOrUpdate())它们根据数据是否有id来判断是insert还是update

删除和查询分别使用的方法是remove()find(Object.class)/load(Object.class)

使用hql的例子:

@Test
public void Test(){
    try(Session session = sf.openSession()){
		Transcation transcation = session.beginTransaction;
        String hql = " FROM Object";
        List<Object> result = session.createQuery(hql, Object.class).getResultList();
        transcation.commit();
    }
}

二、JPA 对象的四种状态

临时状态:刚创建出来,没有与entityManager发生关系,没有被持久化

持久状态:已被持久化,处于entityManager,等同于已经存在在数据库中的数据

删除状态:提交remove方法后,事务提交前

游离状态:commit之后实体的状态,无论怎么修改都不对数据库造成影响

三、基于 JPA 数据库持久化操作

依赖导入配置

<dependency>
	<groupId>org.apache.openjpa</groupId>
    <artifactId>openjpa-all</artifactId>
    <version>3.2.0</version>
</dependency>

创建META-INF/persistence.xml

<?xml version="1.0" encoding="UTF‐8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema‐instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:task="http://www.springframework.org/schema/tas
k"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring‐beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring‐aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring‐context.
xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring‐jdbc.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring‐tx.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring‐jpa.xsd">

    <!‐‐ 1.dataSource 配置数据库连接池‐‐>
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver" />
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/jpa" />
        <property name="user" value="root" />
        <property name="password" value="root"/>
    </bean>

    <!‐‐ 2.配置entityManagerFactory ‐‐>
        <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="packagesToScan" value="cn.itcast.entity" />
        <property name="persistenceProvider">
    <bean class="org.hibernate.jpa.HibernatePersistenceProvider" />
        </property>
        <!‐‐JPA的供应商适配器‐‐>
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="generateDdl" value="false" />
                <property name="database" value="MYSQL" />
                <property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect" />
                <property name="showSql" value="true" />
        </bean>
    	</property>
    </bean>


    <!‐‐ 整合spring data jpa‐‐>
    <jpa:repositories base‐package="cn.itcast.dao"
    transaction‐manager‐ref="transactionManager"
    entity‐manager‐factory‐ref="entityManagerFactory"></jpa:repositories>


    <!‐‐ 3.事务管理器‐‐>
    <!‐‐ JPA事务管理器 ‐‐>
    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="entityManagerFactory" ref="entityManagerFactory" />
    </bean>

    <!‐‐基于注解方式的事务,开启事务的注解驱动
    如果基于注解的和xml的事务都配置了会以注解的优先
    ‐‐>
    <tx:annotation‐driven transaction‐manager="transactionManager"></tx:annotation‐driven>

    <context:component‐scan base‐package="cn.itcast"></context:component‐scan>

    <!‐‐组装其它 配置文件‐‐>

</beans>

持久层操作:

EntityManagerFactory factory;

@Before
public void before(){
	factory = Persistence.createEntityManagerFactory("xmlPersistenceUnitName");
}

@Test
public void test(){
	EntityManager manager = factory.createEntityManager();
    EntityTransaction transaction = manager.getTransaction();
    
}

JPA的entityManager的find方法与getReference方法的区别

find

返回指定的 OID 对应的实体类对象,如果这个实体存在于当前的持久化环境,则返回一个被缓存的对象;否则会创建一个新的 Entity, 并加载数据库中相关信息;若 OID 不存在于数据库中,则返回一个 null。第一个参数为被查询的实体类类型,第二个参数为待查找实体的主键值。

getReference

find()方法类似,不同的是:如果缓存中不存在指定的 Entity, EntityManager 会创建一个 Entity 类的代理,但是不会立即加载数据库中的信息,只有第一次真正使用此Entity 的属性才加载,所以如果此 OID 在数据库不存在,getReference()不会返回 null 值, 而是抛出EntityNotFoundException

find方法类似于hibernateSessionget方法。

getReference方法类似于hibernateSessionload方法。

find()做了一次select的操作,而getReference并没有做有关数据库的操作,而是返回一个代理,这样它就减少了连接数据库和从数据库加载持久状态的开销。

使用jpql的例子:

String jpql="UPDATE Customer set custName=:name where custId=:id";
manager.createQuery(jpql).setParameter("name","李四").setParameter("id",5L).executeUpdate().executeUpdate();
transaction.commit;
四、JPA 中映射关联关系
1.一对一映射

@OneToOne执行一对一映射,比如一个学生对应一个档案,一个档案对应一个学生

@Entity
public class Student {
    
    ...
    @OneToOne(fetch=LAZY)
    @JoinColum(name="archive_id")
    private Archive archive;
    ...
}

fetch=LAZY可以实现该属性懒加载

@JoinColumn 为一对一映射设置连接列。 或者使用@PrimaryKeyJoinColum设置主键连接列

@OneToOne(cascade=CascadeType.ALL,mappedBy="department")主要是用来设置JPA的级联,他和@JoinColumn 是互斥的。JPA中的CascadeType.ALL并不等于**{CascadeType.PESIST,CascadeType.REMOVE,CascadeType.MERGE,CascadeType.REFRESH}**

在Hibernate中调用session.save() or session.update()并不能触发 {CascadeType.PESIST,CascadeType.REMOVE,CascadeType.MERGE,CascadeType.REFRESH} 的级联操作,而能触发CascadeType.ALL的级联。如不希望用CascadeType.ALL,需要使用Hibernate自身对 cascade的注解 @Cascade(value=org.hibernate.annotations.CascadeType.SAVE_UPDATE)

mappedBy被用来定义被拥有方的,被mappedBy定义的事被拥有方,拥有这个属性的是主导者,比如学生是档案的主导者,就可以让Student表做主导,通常还有一个option属性,比如Archive就应该为true代表一个档案是必须有人的,当是一个学生不一定有档案就可以设置option为false。

单向一对一映射

单向一对一映射更像是一个多对一的特殊版本,假如使用注解的形式进行配置会发现一个entity有one to one注解,一个没有,即一个表有外键一个没有外键。

2.一对多映射和多对一映射

一个学校可以有很多学生,很多学生也可以对应一个学校,这就反映了一对多和多对一映射:

@Entity
public class Student {
    
    ...
    @ManyToOne(cascade=CascadeType.ALL)
    @JoinColumns({
        @JoinColumn(name="SCHOOL_NAME", referencedColumnName="Name"),
    	@JoinColumn(name="SCHOOL_ID", referencedColumnName="ID")
    })
    private School school;
    ...
}
@Entity
public class School {
    
    ...
    @OneToMany(targetEntity=Student.class, mappedBy="school")
    @JoinTable(name=“INFO”,
		joinCloums=@JoinColum(name="SCH_ID"),
        inverseJoin=@JoinColum(name="STU_ID"))
    @OrderBy("name ASC")
    @MapKey(name="id")
    private Set<Student> students = new HashSet<Student>;
    ...
}

这个例子里,School通过中间表EMP_INFO来关联Student

当多对一需要多个连接列时可以使用@JoinColumns

@OrderBy用于将一对多映射按属性设置排序。具体实现其实是在sql查询时加入order by。或者通过@OrderColumn进行排序,但可能会出现空数据的情况,比如:

以id作为排序列,但是只有0,3,5这三个列,但是查询到的将会是0,Null,Null,NULL,3,NULL,5

@MapKey(name =“id")设置为使用student id作为Student的映射键组。

3.多对多映射

一个普通公司可以雇佣好几个外包公司,一个外包公司也可以同时给多个公司提供外包服务,这就反映了多对多的映射关系:

@Entity
public class Subcontractor {
    
    ...
    @ManyToMany
    private Company company;
    ...
}
@Entity
@JoinTable(name="COM_EMP",
           joinColumns=@JoinColumn(name="COM_ID"),           inverseJoinColumns=@JoinColumn(name="SUB_ID"))
@AttributeOverride(name="sub_name", 
                   column=@Column(name="SUB_NAME"))
public class Company {
    ...
    @ManyToMany
    private Subcontractor subcontractor;
    ...
}

这种映射往往需要两个中间表,如上所示,这里不多做讨论

@AttributeOverride用于覆盖多对多映射