ORM思想之源码手写篇:仿Hibernate手写ORM框架
前面我们通过跟踪源码的方式剖析了 Hibernate 和 MyBatis 两个框架是如何应用 ORM 思想的,接下来我们自己定义一个简单的 ORM 框架(仿Hibernate),希望能通过这种方式让大家亲自零距离的去应用一下 ORM。
1. ORM 框架的结构设计
- 第一层为配置层:
- orm.cfg.xml 是框架的核心配置文件,主要用来设置数据库连接信息和映射配置文件路径信息
- Xxx.mapper.xml 是框架的映射配置文件,主要用来设置类和表之间以及属性和字段之间的映射关系
- Xxx.java 是带有映射注解的实体类,主要用来设置类和表之间以及属性和字段之间的映射关系,和 Xxx.mapper.xml 的作用一样,只不过采用的是注解方式,两者二选一
- 第二层为解析层:
- XmlUtils 类用来解析 orm.cfg.xml 和 Xxx.mapper.xml 两个配置文件的数据
- AnnotationUtils 类用来解析实体类中的映射注解
- 第三层为封装层:
- Config 类用来封装和存储从 orm.cfg.xml 文件中解析得到的数据
- Mapper 类用来封装和存储从 Xxx.mapper.xml 或实体类中解析得到的映射数据
- 第四层为构建层:
- Configuration 类用来读取核心配置文件并存放在 Config类,使用Config类来构建SessionFactory
- SessionFactory 类用来创建 Connection 连接, 解析映射文件封装到Mapper里,用来获取 Session 对象
- 第四层为功能层:
- SqlUtils 类主要是根据 entity 和 Mapper 来生成SQL语句
- Session 类通过对 JDBC 的封装最终实现增删改查功能
2. ORM 框架的代码实现
2.1 创建 Maven 项目
<groupId>top.lzchao.framework.orm</groupId>
<artifactId>orm-core</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
2.2 编写 pom.xml
<dependencies>
<dependency>
<groupId>org.jsoup</groupId>
<artifactId>jsoup</artifactId>
<version>1.12.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
</plugins>
</build>
2.3 设计注解 @Entity,@Table,@Id, @Colunm
/**
* @author: LzCc
* @blog: https://blog.youkuaiyun.com/qq_41744145
* @description: 识别为关系映射类
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Entity { }
/**
* @author: LzCc
* @blog: https://blog.youkuaiyun.com/qq_41744145
* @description: 用来设置当前类和哪个表对应
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Table {
String name() default "";
}
/**
* @author: LzCc
* @blog: https://blog.youkuaiyun.com/qq_41744145
* @description: 用来设置当前属性和表中哪个字段对应
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
String name();
}
/**
* @author: LzCc
* @blog: https://blog.youkuaiyun.com/qq_41744145
* @description: 用来设置哪个属性对应的字段是主键
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Id { }
2.4 XmlUtils 类
XmlUtils 类是一个基于 Jsoup 的工具类 ,主要用来解析 orm.cfg.xml 和 Xxx.mapper.xml
/**
* @author: LzCc
* @blog: https://blog.youkuaiyun.com/qq_41744145
* @description: 解析XML文件工具类
*/
public class XmlUtils {
/**
* 通过文件的路径获取 xml 的 document 对象
* @param filePath 文件的路径
* @return 返回文档对象
*/
public static Document getXMLByFilePath(String filePath) {
if (null == filePath) { // 空值处理
throw new RuntimeException("【框架异常】核心配置文件路径错误!");
}
try {
// 通过Jsoup.parse获取Document对象
return Jsoup.parse(new File(filePath), "utf-8");
} catch (IOException e) {
throw new RuntimeException("【框架异常】核心配置文件路径错误!");
}
}
/**
* 针对 orm.cfg.xml 文件,获得数据库信息并存到 Map 集合中
* @param document xml 文档对象
* @return
*/
public static Map<String, String> property2Map(Document document) {
Elements elements = document.getElementsByTag("property");// 获取所有的<property>标签
Map<String, String> propConfig = new HashMap<>();// 创建一个Map集合存放和核心配置文件中的信息
for (Element element : elements) {
String key = element.attr("name"); // 获取key值
String value = element.html(); // 获取value
propConfig.put(key, value); // 存入集合
}
return propConfig;
}
/**
* 针对核心文件下的 mapping 标签,获取 映射文件 路径到 Set 集合中
* @param document xml 文档对象
* @param attrName 属性名 package 和 resource
* @return
*/
public static Set<String> mapping2Set(Document document, String attrName) {
Elements elements = document.getElementsByAttribute(attrName); //获取带有attrName属性的标签
Set<String> mappingSet = new HashSet<>(); // 创建一个值不重复的Set集合存放 映射文件路径
for (Element element : elements) {
mappingSet.add(element.attr(attrName));
}
return mappingSet;
}
/**
* 针对映射文件的某标签获取所有实体映射信息
* @param document
*/
public static Map<String, String> mapping2Map(Document document) {
HashMap<String, String> map = new HashMap<>(); // 创建map集合存放实体类映射信息
Map<String, String> ids = mappingId2Map(document); // 获取映射信息id的集合
map.putAll(ids); // 合并集合
Elements elements = document.getElementsByTag("property"); // 获取所有<property>标签的
for (Element element : elements) {
Element child = element.child(0); // 获取标签下面的第一个子节点 <column>
map.put(element.attr("name"), child.attr("name")); // 存入map集合中
}
return map;
}
/**
* 针对映射文件的某标签获取 实体类 id映射信息
* @param document
*/
public static Map<String, String> mappingId2Map(Document document) {
Elements elements = document.getElementsByTag("id"); // 获取id标签节点
HashMap<String, String> map = new HashMap<>(); //存放id映射信息的集合
for (Element element : elements) {
Element child = element.child(0);
map.put(element.attr("name"), child.attr("name"));
}
return map;
}
}
2.5 AnnotationUtils 类
AnnotationUtil 类主要用来通过反射技术解析实体类中的注解,从而获得映射数据,源码
/**
* @author: LzCc
* @blog: https://blog.youkuaiyun.com/qq_41744145
* @description: 注解解析工具
*/
public class AnnotationUtil {
/**
* 判断是否被表示为实体类
* @param entityClass
* @return
*/
public static boolean isEntity(Class entityClass) {
return entityClass.isAnnotationPresent(Entity.class);
}
/**
* 获取路径名
* @param clz
* @return
*/
public static String getClassName(Class clz) {
return clz.getName();
}
/**
* 获取表名
* @param entityClass
* @return
*/
public static String getTableName(Class entityClass) {
if (entityClass.isAnnotationPresent(Table.class)) { //是否存在Table注解
Table annotation = (Table) entityClass.getAnnotation(Table.class);//获取Table注解
return annotation.name();//取值
} else {
throw new RuntimeException("缺少 Table 注解");
}
}
/**
* 得到主键属性和对应的字段
* @param entityClass
* @return
*/
public static Map<String, String> getIdMapper(Class entityClass) {
Map map = new HashMap<String, String>(); // 存放id映射信息的集合
Field[] fields = entityClass.getDeclaredFields(); // 获取所有字段
boolean flag = true;
for (Field field : fields) { // 遍历字段
if (field.isAnnotationPresent(Id.class)) { // 如果是带有Id注解的字段
flag = false;
if (field.isAnnotationPresent(Column.class)) {
String fieldName = field.getName(); // 获取字段名字
Column ormColumn = field.getAnnotation(Column.class); //获取Column注解
String columnName = ormColumn.name(); //取值获得列名
map.put(fieldName, columnName); //存入集合
break;
} else {
throw new RuntimeException("【框架异常】缺少 Column 注解");
}
}
}
if(flag){
throw new RuntimeException("【框架异常】缺少 Id 注解");
}
return map;
}
/**
* 得到类中所有属性和对应的字段
* @param entityClass
* @return
*/
public static Map<String, String> getPropMapping(Class entityClass) {
Map map = new HashMap<String, String>(); // 创建集合存放 属性和列名 的映射关系
Field[] fields = entityClass.getDeclaredFields(); // 获取所有字段
for (Field field : fields) { // 遍历字段
if (field.isAnnotationPresent(Column.class)) { // 是否被标注Column注解
String fieldName = field.getName(); // 获取属性名
Column ormColumn = field.getAnnotation(Column.class);
String columnName = ormColumn.name(); // 获取列名
map.put(fieldName, columnName); // 存入集合
} else {
throw new RuntimeException("【框架异常】缺少 Column 注解");
}
}
return map;
}
}
2.6 Mapper 类
Mapper 类用来封装并存储从 Xxx.mapper.xml 中或从实体类中解析得到的映射信息,哪个表和哪个类映射,哪个字段和哪个属性映射等等
/**
* @author: LzCc
* @blog: https://blog.youkuaiyun.com/qq_41744145
* @description:
*/
public class Mapper {
private String className; //类名
private String tableName; //表名
private Map<String, String> idMapper = new HashMap(); //主键字段和属性
private Map<String, String> propMapping = new HashMap(); //非主键字段和属性
}
2.7 Config 类
Config 类主要用来存储 orm.cfg.xml 配置文件中的信息
/**
* @author: LzCc
* @blog: https://blog.youkuaiyun.com/qq_41744145
* @description: 封装核心配置信息
*/
public class Config {
private Map<String, String> propConfig; //核心配置文件数据
private Set<String> mappingSet; //映射配置文件
private Set<String> entitySet; //实体类
public Map<String, String> getPropConfig() {
return propConfig;
}
public Set<String> getMappingSet() {
return mappingSet;
}
public Set<String> getEntitySet() {
return entitySet;
}
public void configure(String cfgName) {
// 1.获取类路径
String classpath = Config.class.getClassLoader().getResource("").getPath();
// 2.解析配置文件
File file = new File(classpath + cfgName);
if(file.exists()){ // 文件存在
Document document = XmlUtils.getXMLByFilePath(file.getPath()); // 开始解析XML文件
propConfig = XmlUtils.property2Map(document);
mappingSet = XmlUtils.mapping2Set(document, "resource");
entitySet = XmlUtils.mapping2Set(document, "class");
} else {
throw new RuntimeException("【框架异常】核心配置文件未找到");
}
}
}
2.8 Configuration类
Configuration 类用来读取核心配置文件并存放在 Config 类并且使用 Config 类来构SessionFactory
/**
* @author: LzCc
* @blog: https://blog.youkuaiyun.com/qq_41744145
* @description: Configuration 类
*/
public class Configuration {
public Config config;
public Configuration(){
config = new Config();
}
public Configuration configure(){
return configure("orm.cfg.xml");
}
public Configuration configure(String cfgName){
config.configure(cfgName); //读取配置文件
return this;
}
public SessionFactory buildSessionFactory(){
return buildSessionFactory(config);
}
public SessionFactory buildSessionFactory(Config config){
SessionFactory sessionFactory = new SessionFactory();
sessionFactory.setPropConfig(config.getPropConfig());
try{
//解析映射文件
sessionFactory.getMapping(config.getMappingSet(), config.getEntitySet());
} catch (ClassNotFoundException e){
throw new RuntimeException(e.getMessage());
}
return sessionFactory;
}
}
2.9 SessionFactory 类
类用来创建 Connection 连接, 解析映射文件封装到Mapper里,用来获取 Session 对象
/**
* @author: LzCc
* @blog: https://blog.youkuaiyun.com/qq_41744145
* @description: SessionFactory 类
*/
public class SessionFactory {
private Map<String, String> propConfig; //核心配置文件数据
private List<Mapper> mapperList; //实体映射信息
public void setPropConfig(Map<String, String> propConfig) {
this.propConfig = propConfig;
}
/**
* 从 propConfig 获得信息,连接数据库
* @return
* @throws ClassNotFoundException
* @throws SQLException
*/
private Connection getConnection(boolean autoCommit) {
// 底层JDBC获取连接操作
String url = propConfig.get("connection.url");
String driverClass = propConfig.get("connection.driverClass");
String username = propConfig.get("connection.username");
String password = propConfig.get("connection.password");
try {
Class.forName(driverClass);
Connection connection = DriverManager.getConnection(url, username, password);
connection.setAutoCommit(autoCommit); // 设置自动提交事务
return connection;
} catch (Exception e) {
throw new RuntimeException("【框架异常】数据库连接错误!");
}
}
/**
* 获取所有实体映射关系
* @param mappingSet
* @param entitySet
*/
public void getMapping(Set<String> mappingSet, Set<String> entitySet) throws ClassNotFoundException {
List<Mapper> mapperList = new ArrayList<>();
for (String xmlPath : mappingSet) {
String classpath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
Document document = XmlUtils.getXMLByFilePath(classpath + xmlPath); // 解析 mapper.xml 文件
Element element = document.getElementsByTag("class").get(0);// 获取class 根元素
Map<String, String> ids = XmlUtils.mappingId2Map(document);
Map<String, String> properties = XmlUtils.mapping2Map(document);
Mapper mapper = new Mapper(); // 创建Mapper对象并封装
mapper.setIdMapper(ids);
mapper.setPropMapping(properties);
mapper.setClassName(element.attr("name"));
mapper.setTableName(element.attr("table"));
mapperList.add(mapper); //添加集合
}
for (String classPath : entitySet) {
Class entityClass = Class.forName(classPath);
if(AnnotationUtil.isEntity(entityClass)){ // 如果标准@Entity注解说明要解析
String className = AnnotationUtil.getClassName(entityClass); // 获取类名
String tableName = AnnotationUtil.getTableName(entityClass); // 获取表名
Map<String, String> ids = AnnotationUtil.getIdMapper(entityClass);
Map<String, String> mapping = AnnotationUtil.getPropMapping(entityClass);
Mapper mapper = new Mapper();// 创建Mapper对象并封装
mapper.setClassName(className);
mapper.setTableName(tableName);
mapper.setIdMapper(ids);
mapper.setPropMapping(mapping);
mapperList.add(mapper); //添加集合
}
}
this.mapperList = mapperList;
}
public Session openSession(){
return this.openSession(true);
}
public Session openSession(boolean autoCommit){
return new Session(getConnection(autoCommit), mapperList);
}
}
2.10 SqlUtils 类
通过 实体类字节码 和 映射信息对象 来生成对应的SQL语句
/**
* @author: LzCc
* @blog: https://blog.youkuaiyun.com/qq_41744145
* @description: 生成SQL语句
*/
public class SqlUtils {
/**
* insert into 表名 (字段列表 ... ) values(字段列表 ...)
* @param entity
* @param mapper
* @return
*/
public static String insertSQL(Object entity, Mapper mapper) throws IllegalAccessException {
StringBuilder insertSQL = new StringBuilder();
insertSQL.append("insert into " + mapper.getTableName() + " (");
StringBuilder colunmNames = new StringBuilder("");
StringBuilder colunmValues = new StringBuilder("");
Field[] fields = entity.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true); // 暴力访问
String colunmName = mapper.getPropMapping().get(field.getName()); // 获取列名
colunmNames.append(colunmName + ", ");
String colunmValue = field.get(entity).toString(); // 获取值
colunmValues.append("'" + colunmValue + "', ");
}
insertSQL.append(colunmNames.substring(0, colunmNames.lastIndexOf(","))).append(")").append(" values(").append(colunmValues.substring(0, colunmValues.lastIndexOf(","))).append(")");
return insertSQL.toString();
}
/**
* update 表名 set 列名1 = 值1, ... where 列id = 值id
* @param entity
* @param mapper
* @return
*/
public static String updateSQL(Object entity, Mapper mapper) throws IllegalAccessException {
StringBuilder updateSQL = new StringBuilder();
updateSQL.append("update " + mapper.getTableName() + " set ");
StringBuilder colunm = new StringBuilder();
StringBuilder id = new StringBuilder();
Field[] fields = entity.getClass().getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
String colunmId = mapper.getIdMapper().get(field.getName());
if(colunmId == null){
String colunmNames = mapper.getPropMapping().get(field.getName());
String colunmValue = field.get(entity).toString(); // 获取值
colunm.append(colunmNames + " = '" + colunmValue + "', ");
} else {
String idVal = (String) mapper.getIdMapper().values().toArray()[0];
id.append(idVal + " = '" + field.get(entity) + "'");
}
}
updateSQL.append(colunm.substring(0, colunm.lastIndexOf(","))).append(" where ");
updateSQL.append(id);
return updateSQL.toString();
}
/**
* delete from 表名 where 表主键 = 值
*
* @param entity
* @param mapper
* @return
*/
public static String deleteSQL(Object entity, Mapper mapper) throws IllegalAccessException, NoSuchFieldException {
StringBuilder deleteSQL = new StringBuilder();
deleteSQL.append("delete from " + mapper.getTableName() + " where ");
String idProp = (String) mapper.getIdMapper().keySet().toArray()[0];
String idColumn = (String) mapper.getIdMapper().values().toArray()[0];
// 5. 得到主键的值
Field field = entity.getClass().getDeclaredField(idProp);
field.setAccessible(true);
String idVal = field.get(entity).toString();
deleteSQL.append(idColumn + " = '" + idVal + "'");
return deleteSQL.toString();
}
/**
* select * from 表名 where 表主键 = 值
*
* @param entity
* @param mapper
* @return
*/
public static String findOneSQL(Class entity, Object id, Mapper mapper) throws IllegalAccessException, NoSuchFieldException {
StringBuilder findOneSQL = new StringBuilder();
findOneSQL.append("select * from " + mapper.getTableName() + " where ");
String idColumn = (String) mapper.getIdMapper().values().toArray()[0];
findOneSQL.append(idColumn + " = " + id);
return findOneSQL.toString();
}
}
2.11 Session
类通过对 JDBC 的封装最终实现增删改查功能
/**
* @author: LzCc
* @blog: https://blog.youkuaiyun.com/qq_41744145
* @description: Session
*/
public class Session {
private Connection conn; // 连接对象
List<Mapper> mapperList; // 映射信息对象集合
public Session(Connection conn, List<Mapper> mapperList) {
this.conn = conn;
this.mapperList = mapperList;
}
/**
* 保存用户
* @param entity
*/
public void save(Object entity) {
String insertSQL = null;
for (Mapper mapper : mapperList) { // 遍历映射信息集合
if (mapper.getClassName().equals(entity.getClass().getName())) { //找到与实体类对应的映射信息
try {
insertSQL = SqlUtils.insertSQL(entity, mapper); // 通过工具类生成SQL语句
} catch (IllegalAccessException e) {
throw new RuntimeException(e.getMessage());
}
}
}
// 把 sql 语句打印到控制台
System.out.println("【ORM-save】" + insertSQL);
// 通过 JDBC 发送并执行 sql
PreparedStatement statement = null;
try {
statement = conn.prepareStatement(insertSQL);
statement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 更新用户
* @param entity
*/
public void update(Object entity) {
String updateSQL = null;
for (Mapper mapper : mapperList) { // 遍历映射信息集合
if (mapper.getClassName().equals(entity.getClass().getName())) { //找到与实体类对应的映射信息
try {
updateSQL = SqlUtils.updateSQL(entity, mapper); // 通过工具类生成SQL语句
} catch (IllegalAccessException e) {
throw new RuntimeException(e.getMessage());
}
}
}
// 把 sql 语句打印到控制台
System.out.println("【ORM-update】" + updateSQL);
// 通过 JDBC 发送并执行 sql
PreparedStatement statement = null;
try {
statement = conn.prepareStatement(updateSQL);
statement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 根据id删除用户
* @param entity
*/
public void delete(Object entity) {
String deleteSQL = null;
for (Mapper mapper : mapperList) { // 遍历映射信息集合
if (mapper.getClassName().equals(entity.getClass().getName())) { //找到与实体类对应的映射信息
try {
deleteSQL = SqlUtils.deleteSQL(entity, mapper); // 通过工具类生成SQL语句
} catch (IllegalAccessException e) {
throw new RuntimeException(e.getMessage());
} catch (NoSuchFieldException e) {
throw new RuntimeException(e.getMessage());
}
}
}
// 把 sql 语句打印到控制台
System.out.println("【ORM-delete】" + deleteSQL);
// 通过 JDBC 发送并执行 sql
PreparedStatement statement = null;
try {
statement = conn.prepareStatement(deleteSQL);
statement.executeUpdate();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 根据id查询
* @param clz 字节码
* @param id 主键值
*/
public <T> T get(Class<T> clz, Object id) {
String findOneSQL = null;
for (Mapper mapper : mapperList) { // 遍历映射信息集合
if (mapper.getClassName().equals(clz.getName())) { //找到与实体类对应的映射信息
try {
findOneSQL = SqlUtils.findOneSQL(clz, id, mapper); // 通过工具类生成SQL语句
} catch (IllegalAccessException e) {
throw new RuntimeException(e.getMessage());
} catch (NoSuchFieldException e) {
throw new RuntimeException(e.getMessage());
}
}
}
// 把 sql 语句打印到控制台
System.out.println("【ORM-select】" + findOneSQL);
// 通过 JDBC 发送并执行 sql
PreparedStatement statement = null;
try {
statement = conn.prepareStatement(findOneSQL);
ResultSet rs = statement.executeQuery();
Object obj = null;
if(rs.next()){
obj = clz.newInstance();
for(Mapper mapper:mapperList){
if (mapper.getClassName().equals(clz.getName())) {
Map<String, String> propMapping = mapper.getPropMapping();
for (String key : propMapping.keySet()) {
String column = propMapping.get(key);
Object value = rs.getObject(column);
Field field = clz.getDeclaredField(key);
field.setAccessible(true);
field.set(obj, value);
}
break;
}
}
}
rs.close();
statement.close();
return (T) obj;
} catch (SQLException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
return null;
}
/**
* 释放资源
*/
public void close() {
if (null != conn) {
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
3. ORM 框架的测试使用
手写 ORM框架 主要是为了来体现 ORM思想,并不是为了开发一个成熟的持久层框架出来,因此很多逻辑并不完善,很多情况也未去考虑,接下来测试下框架
3.1 创建 Maven工程
3.2 编写 pom.xml
<dependencies>
<dependency>
<groupId>top.lzchao.framework.orm</groupId>
<artifactId>orm-core</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
</dependencies>
我们前面把 ORM-core 框架打成 jar 包并 install 到了本地 Maven 仓库中,因此在使用该框架时需要从本地仓库进行加载
3.3 orm.cfg.xml
<?xml version='1.0' encoding='utf-8'?>
<orm-configuration>
<property name="connection.url">jdbc:mysql://localhost:3306/test?serverTimezone=UTC</property>
<property name="connection.driverClass">com.mysql.cj.jdbc.Driver</property>
<property name="connection.username">root</property>
<property name="connection.password">123456</property>
<mapping class="top.lzchao.orm.entity.Book"/> <!--带有映射注解的实体类-->
<mapping resource="top/lzchao/orm/entity/Book.hbm.xml"/> <!--映射配置文件-->
</orm-configuration>
这是框架的核心配置文件,我们既采用了 xml 方式配置映射数据,也采用了注解方式在实体类中配置映射数据,最后可以二选一分别进行功能测试。
实体类和映射配置文件
/**
* @author: LzCc
* @blog: https://blog.youkuaiyun.com/qq_41744145
* @description: Book 实体类
*/
@Entity
@Table(name = "t_book")
public class Book {
@Id
@Column(name = "bid")
private int id; //主键
@Column(name = "bname")
private String name; //图书名字
@Column(name = "author")
private String author; //图书作者
@Column(name = "price")
private double price; //图书价格
// getter、setter...
// toString...
<?xml version='1.0' encoding='utf-8'?>
<orm-mapping>
<class name="top.lzchao.orm.entity.Book" table="t_book">
<id name="id">
<column name="bid"/>
</id>
<property name="name">
<column name="bname"/>
</property>
<property name="author">
<column name="author"/>
</property>
<property name="price">
<column name="price"/>
</property>
</class>
</orm-mapping>
注意:对于同一个表或实体类,不需要既进行 xml 配置,又进行注解配置,二选一即可,这里同时进行配置只是为了测试方便。
4. 测试类
/**
* @author: LzCc
* @blog: https://blog.youkuaiyun.com/qq_41744145
* @description: ORM框架测试类
*/
public class BookDao {
private SessionFactory factory;
@Before
public void init() {
//1. 创建一个 Configuration 对象,解析 hibernate 的核心配置文件
Configuration cfg = new Configuration().configure("orm.cfg.xml");
//2. 创建 SessinFactory 对象,解析映射信息并生成基本的 sql
factory = cfg.buildSessionFactory();
}
@Test
public void testSave() {
//3. 得到 Session 对象,该对象具有增删改查的方法
Session session = factory.openSession();
//4. 保存数据
Book book = new Book();
book.setId(1);
book.setName("Spring5核心原理");
book.setAuthor("TOM");
book.setPrice(119.9);
session.save(book);
//5. 释放资源
session.close();
}
@Test
public void testFindOne() {
//3. 得到 Session 对象,该对象具有增删改查的方法
Session session = factory.openSession();
//4. 查询数据
Book book = session.get(Book.class, 1);
System.out.println(book);
//5. 释放资源
session.close();
}
@Test
public void testUpdate() {
//3. 得到 Session 对象,该对象具有增删改查的方法
Session session = factory.openSession();
//4. 更新数据
Book book = new Book();
book.setId(1);
book.setName("Spring5核心原理");
book.setAuthor("TOM");
book.setPrice(69.9);
session.update(book);
//5. 释放资源
session.close();
}
@Test
public void testDelete() {
//3. 得到 Session 对象,该对象具有增删改查的方法
Session session = factory.openSession();
//4. 删除数据
Book book = new Book();
book.setId(1);
session.delete(book);
//5. 释放资源
session.close();
}
}
save():
update():
findOne():
delete():