Spring ORM
Outline:
- object-relational mapping
- Hibernate、MyBatis
- JPA (Java Persistence API)
- APPENDIX: 一些好用的ORM Tips
ref: Spring In Action
JDBC | Hibernate | JPA | MyBatis |
---|---|---|---|
DataSource | SessionFactory | EntityManagerFactory | SqlSessionFactory |
Connection | Session | 带有@PersistenceContext 注解的EntityManager 代理类 |
SqlSession |
Hibernate
配置
获得
org.hibernate.Session
接口的实现类, 这需要我们创建一个LocalSessionFactoryBean
,它会自动创建一个SessionFactory
在Hibernate中,
Session
是封装了一个JDBCConnection
的实例,而SessionFactory
是封装了JDBCDataSource
的实例,即SessionFactory
持有连接池,每次需要操作数据库的时候,SessionFactory
创建一个新的Session
,相当于从连接池获取到一个新的Connection
在hibernate4,我们一般用:
org.springframework.orm.hibernate4.LocalSessionFactoryBean
Hibernate作为ORM框架,可以替代
JdbcTemplate
,但Hibernate仍然需要JDBC驱动,所以,我们需要引入JDBC驱动、连接池,以及Hibernate本身, 并配置DataSource定义映射关系:XML、注解(JPA、Hibernate)
配置数据源等
创建DataSource、引入JDBC配置文件,以及启用声明式事务:
1 |
|
SessionFactory
使用org.springframework.orm.hibernate4.LocalSessionFactoryBean
1 |
|
查询
三类查询:
HQL:hibernate query language,即hibernate提供的面向对象的查询语言
1
select/update/delete…… from …… where …… group by …… having …… order by …… asc/desc
QBC查询: query by criteria 完全面向对象的查询
本地SQL查询
@Repository 的作用
@Component
转换成Spring的统一异常
1
2
3
4
public BeanPostProcessor persistenceTranslation(){
return new PersistenceExceptionTranslationPostProcessor();
}
MyBatis
MyBatis是半自动的ORM,只负责把ResultSet自动映射到Java Bean,或者自动填充Java Bean参数,但仍需自己写出SQL
可以用注解或XML配置, 后者比较繁琐,不介绍了
application-**.yml
中的配置- 配置数据源
mapper-locations
指定
定义接口(使用注解
@Mapper
)mapper/***Mapper.xml
配置
配置数据源等
SqlSessionFactory
使用MyBatis的核心就是创建SqlSessionFactory
,这里我们需要创建的是SqlSessionFactoryBean
:
1 | @Bean |
因为MyBatis可以直接使用Spring管理的声明式事务,因此,创建事务管理器和使用JDBC是一样的:
1 | @Bean |
Mapper
和Hibernate不同的是,MyBatis使用Mapper来实现映射,而且Mapper必须是接口。我们以User类为例,在User类和users表之间映射的UserMapper编写如下:
1 | public interface UserMapper { |
@MapperScan
Mybatis将Mapper接口动态代理,生成实现类。 要识别到Mapper接口,有两种方法:
使用
@Mapper
标识Mapper接口, 比较繁琐:1
2
3
4
5
public interface UserDAO {
//代码
}在某个配置类中启用
@MapperScan()
,并指定Mapper接口所在的包, 该包下的所有接口都会被动态代理:1
2
3
4
5@MapperScan("com.itranswarp.learnjava.mapper")
...其他注解...
public class AppConfig {
...
}- 可以扫描多个包:
@MapperScan({"com.kfit.demo","com.kfit.user"})
- 这个注解实际上会生成
MapperFactoryBean
,后者会自动创建包下所有Mapper的实现类
- 可以扫描多个包:
:
Mapper语法
在定义了接口方法后,还需要明确写出查询的SQL, SQL的每个参数都与方法参数按名称对应
例如,方法参数id的名字通过注解
@Param()
标记为id
,则SQL语句里将来替换的占位符就是#{id}
如果有多个参数,那么每个参数命名后直接在SQL中写出对应的占位符即可:
1
2@Select("SELECT * FROM users LIMIT #{offset}, #{maxResults}")
List<User> getAll(@Param("offset") int offset, @Param("maxResults") int maxResults);
SELECT
MyBatis将ResultSet的每一行转换为Domain实例, 转换规则当然是按列名和属性名对应。如果列名和属性名不同, 需要用别名:
对于SELECT语句:
1
2-- 列名是created_time,属性名是createdAt:
SELECT id, name, email, created_time AS createdAt FROM users
INSERT
MyBatis插入时, 需要将对象的属性转换成列:
1 |
|
- 在SQL中以
#{obj.property}
的方式写占位符
如果表的id是自增主键,那么,我们在SQL中不传入id,但希望获取插入后的主键,需要再加一个@Options
注解:
1 |
|
keyProperty
:JavaBean 的属性keyColumn
: 数据库的主键列名
UPDATE
执行UPDATE和DELETE语句相对比较简单,我们定义方法如下:
1 |
|
DELETE
1 |
|
使用Mapper
在Service层直接注入Mapper, 使用Mapper提供的方法
1 |
|
JPA
- JPA的宗旨是为POJO提供持久化标准规范
- JPQL(Java Persistence Query Language)
- JPQL就是一种查询语言,具有与 SQL 相类似的特征
- JPA语法大全
配置
步骤:
- 配置数据源等
- 创建
EntityManagerFactoryBean
, 它会生成一个SessionFactory
- 将
SessionFactory
注入到JpaTransactionManager
, 以实现声明式事务
- 使用Hibernate时,我们需要创建一个
LocalSessionFactoryBean
,并让它再自动创建一个SessionFactory
。使用JPA也是类似的,我们需要创建一个EntityManagerFactoryBean
,并让它再自动创建一个EntityManagerFactory
,EntityManagerFactory
: 是个工厂Bean, 会创建创建EntityManager
org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean
- `
org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter
javax.persistence.EntityManager
EntityManager
不是线程安全的, Spring遇到标注了@PersistenceContext
的EntityManager
会自动注入代理,该代理会在必要的时候自动打开EntityManager
。换句话说,多线程引用的EntityManager
虽然是同一个代理类,但该代理类内部针对不同线程会创建不同的EntityManager
实例。因此,标注了@PersistenceContext
的EntityManager
可以被多线程安全地共享。@PersistenceUnit
@PersistenceContext
配置数据源等
在AppConfig
中启用声明式事务管理,创建DataSource
:
1 |
|
EntityManagerFactory
1 |
|
JpaTransactionManager
Spring Data JPA
1 | <dependency> |
- 加注解
@EnableJpaRepositories
- 会扫描
org.springframework.data.repository.Repository
接口
- 会扫描
- 继承接口
org.springframework.data.jpa.repository.JpaRepository
编写自定义的查询方法
- 定义查询方法,无需实现
- 领域特定语言(domain-specific language,DSL),spring data的命名约定
- 查询动词 + 主题 + 断言
- 查询动词:get、read、find、count
- 声明自定义查询 不符合方法命名约定时,或者命名太长时:
@Query(“select ...”)
- 使用EntityManager直接低层实现
- 接口名+Impl的实现类
实体类
需要添加注解来告诉ORM如何把实体类映射到表记录
- 作为映射使用的JavaBean,所有属性都使用包装类型而不是基本类型( Mybatis是个例外,这是因为它不是全ORM框架 )
- 如果一个JavaBean被用于映射,我们就标记一个
@Entity
。默认情况下,实体类User映射的表名是user
,如果实际的表名不同,例如实际表名是users
,可以追加一个@Table(name="users")
表示 - 对于主键,还需要用
@Id
标识,自增主键再追加一个@GeneratedValue
,以便Hibernate能读取到自增主键的值 - 每个属性到数据库列的映射用
@Column()
标识,nullable
指示列是否允许为NULL
,updatable
指示该列是否允许被用在UPDATE
语句,length
指示String
类型的列的长度(如果没有指定,默认是255
)
示例:
1 |
|
Appendex
MySQL与JAVA数据类型对应关系
These MySQL Data Types | Can always be converted to these Java types |
---|---|
CHAR, VARCHAR, BLOB, TEXT, ENUM, and SET |
java.lang.String, java.io.InputStream, java.io.Reader, java.sql.Blob, java.sql.Clob |
FLOAT, REAL, DOUBLE PRECISION, NUMERIC, DECIMAL, TINYINT, SMALLINT, MEDIUMINT, INTEGER, BIGINT |
java.lang.String, java.lang.Short, java.lang.Integer, java.lang.Long, java.lang.Double, java.math.BigDecimal |
DATE, TIME, DATETIME, TIMESTAMP |
java.lang.String, java.sql.Date, java.sql.Timestamp |
MYSQL存URL最佳类型
MySQL 5.0.3及更高版本中VARCHAR的有效最大长度受最大行大小(65,535字节,在所有列之间共享)和使用的字符集的限制。
所以,存储url最佳类型为:
< MySQL 5.0.3 use TEXT
>= MySQL 5.0.3 use VARCHAR(2083)