1. 介绍
在当今的软件开发中,与数据库进行交互是一项至关重要的任务。开发人员需要能够轻松、高效地执行数据库操作,同时又要保持代码的可维护性和灵活性。这正是MyBatis这个数据库访问框架应运而生的背景所在。
背景
数据库是许多应用程序的核心组成部分,用于存储和管理数据。然而,与数据库进行交互通常涉及到编写大量的SQL查询语句,以及处理结果集和数据库连接等繁琐的任务。这不仅使开发工作复杂化,还容易引入安全漏洞和性能问题。
在过去,JDBC(Java Database Connectivity)是与数据库交互的主要方式,它要求开发人员手动管理数据库连接、编写SQL语句和处理结果集。这种方式存在以下挑战:
冗余的代码: 大量的重复性代码需要编写,包括数据库连接的打开和关闭、SQL语句的编写等。
硬编码的SQL: SQL语句通常是硬编码在Java代码中的,这使得维护和修改变得困难。
安全性问题: 由于直接拼接SQL语句,容易受到SQL注入攻击的威胁。
缺乏灵活性: 难以处理复杂的数据库操作,如对象关系映射(ORM)和复杂的查询。
MyBatis的出现解决了这些问题,为Java开发人员提供了一个强大的数据库访问框架,它背后的核心理念是将SQL语句从Java代码中解耦,同时提供了丰富的映射和查询功能。
MyBatis的特点
MyBatis具有许多优点,使其成为数据库访问的首选框架之一:
SQL和Java代码的分离: MyBatis允许将SQL语句从Java代码中独立出来,存储在XML文件或注解中,使代码更易维护和管理。
强大的参数映射: MyBatis支持多种参数映射方式,包括基本类型、POJO对象和Map等,使得传递参数变得灵活。
灵活的结果映射: 可以将查询结果映射到Java对象中,支持一对一、一对多和多对多等关系映射。
自动资源管理: MyBatis自动管理数据库连接的打开和关闭,减轻了开发人员的负担。
动态SQL支持: MyBatis允许在SQL中使用动态条件,根据不同情况生成不同的SQL语句。
性能优化: MyBatis提供了缓存机制,可以提高查询性能,同时还支持延迟加载。
丰富的插件机制: 可以通过插件扩展MyBatis的功能,满足不同项目的需求。
在数据库中,数据通常存储在多个相关联的表中,这些表之间存在各种复杂的关系,如一对一、一对多和多对多。为了有效地操作这些复杂的数据模型,引入了关联关系映射的概念,它在复杂数据模型中扮演着至关重要的角色。
什么是关联关系映射?
关联关系映射是将多个表之间的关联关系映射到程序中的对象模型的过程。它的核心目标是:
将数据库表之间的关系抽象成对象之间的关系。
允许通过对象操作来进行数据库操作,而不是直接使用SQL语句。
这个概念的核心思想是将数据库的复杂性隐藏在应用程序的背后,使开发人员能够更自然地处理数据,而不必深入了解数据库的结构和关系。
为什么关联关系映射在复杂数据模型中重要?
在复杂的数据模型中,表之间的关系往往非常复杂,可能包括:
一对一关系: 例如,一个用户只有一个个人详细信息,或一个订单只有一个送货地址。
一对多关系: 例如,一个作者可以有多本书,一个班级可以有多名学生。
多对多关系: 例如,多名学生可以选择多门课程,需要通过关联表进行映射。
在处理这些复杂的关系时,直接使用原始的SQL查询和结果集映射将变得非常繁琐和复杂。这里是为什么关联关系映射如此重要的原因:
抽象复杂性: 关联关系映射将数据库的复杂性抽象成了对象模型,使开发人员无需深入了解数据库细节,从而简化了开发过程。
提高可维护性: 通过将关系映射到对象,可以更容易地维护代码,因为对象模型更加直观和可理解。
提高开发效率: 关联关系映射框架可以自动生成大部分数据库操作代码,减少了手动编写SQL语句的工作量。
增加灵活性: 关联关系映射允许进行高级查询和复杂操作,而不需要深入了解SQL的复杂性。
降低错误风险: 通过将关系映射到对象,可以减少手动编写SQL时可能引入的错误,如拼写错误或SQL注入。
总之,关联关系映射在复杂数据模型中是非常重要的,它提供了一种更高级、更抽象的方式来处理数据库操作,使开发人员能够更轻松地应对复杂性,提高代码的可维护性和开发效率。这正是许多现代应用程序开发中不可或缺的一部分。
2. 一对一关系映射
一对一关系映射是指两个实体之间存在唯一的对应关系,即一个实体只能与另一个实体相对应。例如,一个人只能有一个身份证号码,一个身份证号码也只能对应一个人。
一对一关系映射: 在数据库中,可以通过在一个实体表中添加一个外键来映射一对一关系。例如,假设我们有两个实体表:Person(人)和IDCard(身份证)。每个人只能有一个身份证,而每个身份证也只能对应一个人。我们可以在Person表中添加一个外键IDCardID,将其与IDCard表的主键IDCardID关联起来。这样,每个Person实体都会有一个唯一的IDCardID,从而建立了一对一的关系。 配置generatoeConfig文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd" > <generatorConfiguration> <!-- 引入配置文件 --> <properties resource="jdbc.properties"/> <!--指定数据库jdbc驱动jar包的位置--> <classPathEntry location="C:\\temp2\\mvn_repository\\mysql\\mysql-connector-java\\5.1.44\\mysql-connector-java-5.1.44.jar"/> <!-- 一个数据库一个context --> <context id="infoGuardian"> <!-- 注释 --> <commentGenerator> <property name="suppressAllComments" value="true"/><!-- 是否取消注释 --> <property name="suppressDate" value="true"/> <!-- 是否生成注释代时间戳 --> </commentGenerator> <!-- jdbc连接 --> <jdbcConnection driverClass="${jdbc.driver}" connectionURL="${jdbc.url}" userId="${jdbc.username}" password="${jdbc.password}"/> <!-- 类型转换 --> <javaTypeResolver> <!-- 是否使用bigDecimal, false可自动转化以下类型(Long, Integer, Short, etc.) --> <property name="forceBigDecimals" value="false"/> </javaTypeResolver> <!-- 01 指定javaBean生成的位置 --> <!-- targetPackage:指定生成的model生成所在的包名 --> <!-- targetProject:指定在该项目下所在的路径 --> <javaModelGenerator targetPackage="com.zking.model" targetProject="src/main/java"> <!-- 是否允许子包,即targetPackage.schemaName.tableName --> <property name="enableSubPackages" value="false"/> <!-- 是否对model添加构造函数 --> <property name="constructorBased" value="true"/> <!-- 是否针对string类型的字段在set的时候进行trim调用 --> <property name="trimStrings" value="false"/> <!-- 建立的Model对象是否 不可改变 即生成的Model对象不会有 setter方法,只有构造方法 --> <property name="immutable" value="false"/> </javaModelGenerator> <!-- 02 指定sql映射文件生成的位置 --> <sqlMapGenerator targetPackage="com.zking.mapper" targetProject="src/main/java"> <!-- 是否允许子包,即targetPackage.schemaName.tableName --> <property name="enableSubPackages" value="false"/> </sqlMapGenerator> <!-- 03 生成XxxMapper接口 --> <!-- type="ANNOTATEDMAPPER",生成Java Model 和基于注解的Mapper对象 --> <!-- type="MIXEDMAPPER",生成基于注解的Java Model 和相应的Mapper对象 --> <!-- type="XMLMAPPER",生成SQLMap XML文件和独立的Mapper接口 --> <javaClientGenerator targetPackage="com.zking.mapper" targetProject="src/main/java" type="XMLMAPPER"> <!-- 是否在当前路径下新加一层schema,false路径com.oop.eksp.user.model, true:com.oop.eksp.user.model.[schemaName] --> <property name="enableSubPackages" value="false"/> </javaClientGenerator> <!-- 配置表信息 --> <!-- schema即为数据库名 --> <!-- tableName为对应的数据库表 --> <!-- domainObjectName是要生成的实体类 --> <!-- enable*ByExample是否生成 example类 --> <!--<table schema="" tableName="t_book" domainObjectName="Book"--> <!--enableCountByExample="false" enableDeleteByExample="false"--> <!--enableSelectByExample="false" enableUpdateByExample="false">--> <!--<!– 忽略列,不生成bean 字段 –>--> <!--<!– <ignoreColumn column="FRED" /> –>--> <!--<!– 指定列的java数据类型 –>--> <!--<!– <columnOverride column="LONG_VARCHAR_FIELD" jdbcType="VARCHAR" /> –>--> <!--</table>--> <table schema="" tableName="t_hibernate_book" domainObjectName="HBook" enableCountByExample="false" enableDeleteByExample="false" enableSelectByExample="false" enableUpdateByExample="false"> </table> <table schema="" tableName="t_hibernate_book_category" domainObjectName="HBookCategory" enableCountByExample="false" enableDeleteByExample="false" enableSelectByExample="false" enableUpdateByExample="false"> </table> <table schema="" tableName="t_hibernate_category" domainObjectName="HCategory" enableCountByExample="false" enableDeleteByExample="false" enableSelectByExample="false" enableUpdateByExample="false"> </table> <table schema="" tableName="t_hibernate_order" domainObjectName="Order" enableCountByExample="false" enableDeleteByExample="false" enableSelectByExample="false" enableUpdateByExample="false"> </table> <table schema="" tableName="t_hibernate_order_item" domainObjectName="OrderItem" enableCountByExample="false" enableDeleteByExample="false" enableSelectByExample="false" enableUpdateByExample="false"> </table> </context> </generatorConfiguration>
OrderItemVo
package com.liao.vo; import com.liao.model.Order; import com.liao.model.OrderItem; public class OrderItemVo extends OrderItem { private Order order; public Order getOrder() { return order; } public void setOrder(Order order) { this.order = order; } }
OrderItemMapper.xml
<resultMap id="OrderItemMap" type="com.liao.vo.OrderItemVo" > <result column="order_item_id" property="orderItemId" ></result> <result column="product_id" property="productId" ></result> <result column="quantity" property="quantity" ></result> <result column="oid" property="oid" ></result> <association property="order" javaType="com.liao.model.Order"> <result column="order_id" property="orderId" ></result> <result column="order_no" property="orderNo" ></result> </association> </resultMap> <select id="selectByBiid" resultMap="OrderItemMap" parameterType="java.lang.Integer" > SELECT * FROM t_hibernate_order o , t_hibernate_order_item oi WHERE o.order_id = oi.oid AND oi.order_item_id = #{oiid} </select>
OrderItemBiz
package com.liao.biz; import com.liao.vo.OrderItemVo; public interface OrderItemBiz { OrderItemVo selectByBiid(Integer oiid); }
实现OrderItemBiz接口,创建 OrderItemBizImpl
package com.liao.biz; import com.liao.mapper.OrderItemMapper; import com.liao.vo.OrderItemVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class OrderItemBizImpl implements OrderItemBiz { @Autowired private OrderItemMapper orderItemMapper; @Override public OrderItemVo selectByBiid(Integer oiid) { return orderItemMapper.selectByBiid(oiid); } }
测试
package com.liao.biz; import com.liao.vo.OrderItemVo; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations={"classpath:spring-context.xml"}) public class Test01 { @Autowired private OrderItemBiz orderItemBiz; @Before public void setUp() throws Exception { } @After public void tearDown() throws Exception { } @Test public void selectByBiid() { OrderItemVo orderItemVo = orderItemBiz.selectByBiid(27); System.out.println(orderItemVo); System.out.println(orderItemVo.getOrder()); } }
3. 一对多关系映射
一对多关系映射是指一个实体可以与多个实体相对应,而多个实体只能与一个实体相对应。例如,一个班级可以有多个学生,但一个学生只能属于一个班级。
一对多关系映射: 在数据库中,可以通过在多的一方实体表中添加一个外键来映射一对多关系。例如,假设我们有两个实体表:Department(部门)和Employee(员工)。一个部门可以有多个员工,而一个员工只能属于一个部门。我们可以在Employee表中添加一个外键DepartmentID,将其与Department表的主键DepartmentID关联起来。这样,每个Employee实体都会有一个对应的DepartmentID,从而建立了一对多的关系。 OrdeVo
package com.liao.vo; import com.liao.model.Order; import com.liao.model.OrderItem; import java.util.ArrayList; import java.util.List; public class OrderVo extends Order { private List<OrderItem> orderItems = new ArrayList<>(); public List<OrderItem> getOrderItems() { return orderItems; } public void setOrderItems(List<OrderItem> orderItems) { this.orderItems = orderItems; } }
OrderMapper.xml
<resultMap id="OrderVoMap" type="com.liao.vo.OrderVo"> <result column="order_id" property="orderId" ></result> <result column="order_no" property="orderNo" ></result> <collection property="orderItems" ofType="com.liao.model.OrderItem"> <result column="order_item_id" property="orderItemId" ></result> <result column="product_id" property="productId" ></result> <result column="quantity" property="quantity" ></result> <result column="oid" property="oid" ></result> </collection> </resultMap> <select id="selectByOid" resultMap="OrderVoMap" parameterType="java.lang.Integer" > SELECT * FROM t_hibernate_order o , t_hibernate_order_item oi WHERE o.order_id = oi.oid AND o.order_id = #{oid} </select>
在自动生成的 OrderMapper接口中进行增加以下代码,如下:
OrderVo selectByOid(@Param("oid") Integer oid);
OrderBiz 接口
package com.liao.biz; import com.liao.vo.OrderVo; public interface OrderBiz { OrderVo selectByOid(Integer oid); }
实现 OrderBiz 接口 创建 OrderBizImpl
package com.liao.biz; import com.liao.mapper.OrderMapper; import com.liao.vo.OrderVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class OrderBizImpl implements OrderBiz { @Autowired private OrderMapper orderMapper; @Override public OrderVo selectByOid(Integer oid) { return orderMapper.selectByOid(oid); } }
测试
@Autowired private OrderBiz orderBiz; @Test public void selectByOid() { OrderVo orderVo = orderBiz.selectByOid(7); System.out.println(orderVo); orderVo.getOrderItems().forEach(System.out::println);
4. 多对多关系映射
多对多关系映射是指两个实体之间存在多对多的对应关系,即一个实体可以与多个实体相对应,同时一个实体也可以与多个实体相对应。例如,一个学生可以选择多门课程,而一门课程也可以有多个学生选修。在数据库中,多对多关系通常需要通过引入第三个实体(中间表)来实现。
多对多关系映射: 在数据库中,多对多关系通常需要通过引入第三个实体(中间表)来实现。例如,假设我们有两个实体表:Student(学生)和Course(课程)。一个学生可以选择多门课程,而一门课程也可以有多个学生选修。我们可以创建一个名为StudentCourse的中间表,其中包含两个外键:StudentID和CourseID,分别与Student表和Course表的主键关联起来。这样,每个学生可以在StudentCourse表中有多个对应的CourseID,同时每门课程也可以在StudentCourse表中有多个对应的StudentID,从而建立了多对多的关系。 HBookMapper.xml
<resultMap id="HBookVoMap" type="com.liao.vo.HbookVo" > <result column="book_id" property="bookId"></result> <result column="book_name" property="bookName"></result> <result column="price" property="price"></result> <collection property="categories" ofType="com.liao.model.Category"> <result column="category_id" property="categoryId"></result> <result column="category_name" property="categoryName"></result> </collection> </resultMap> <select id="selectByBookId" resultMap="HBookVoMap" parameterType="java.lang.Integer" > SELECT * FROM t_hibernate_book b, t_hibernate_book_category bc , t_hibernate_category c WHERE b.book_id = bc.bid AND bc.cid = c.category_id AND b.book_id = #{bid} </select>
HBookMapper
HbookVo selectByBookId(@Param("bid") Integer bid);
创建 HBookBiz
package com.liao.biz; import com.liao.vo.HbookVo; import org.apache.ibatis.annotations.Param; public interface HBookBiz { HbookVo selecByBookId(@Param("bid") Integer bid); }
实现 HBookBiz 接口,创建 HBookBizImpl
package com.liao.biz; import com.liao.mapper.HBookMapper; import com.liao.vo.HbookVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class HBookBizImpl implements HBookBiz { @Autowired private HBookMapper hBookMapper; @Override public HbookVo selecByBookId(Integer bid) { return hBookMapper.selectByBookId(bid); } }
测试
@Autowired private HBookBiz hbookBiz; @Test public void selectByBookId() { HbookVo hBookVo = hbookBiz.selecByBookId(8); System.out.println(hBookVo); hBookVo.getCategories().forEach(System.out::println);
5. 总结
MyBatis是一个强大的数据库访问框架,其中的关联关系映射是其核心功能之一。在处理复杂的数据模型时,MyBatis关联关系映射具有重要性和灵活性,如下所总结:
重要性:
1. 数据模型的抽象:
- MyBatis将数据库表之间的关系抽象成对象之间的关系,使开发人员无需深入了解数据库的复杂性,从而简化了开发过程。
2. 提高可维护性:
- 通过将关系映射到对象,代码更加直观和可理解,使得代码的维护和修改变得更加容易。
3. 提高开发效率:
- MyBatis自动生成大部分数据库操作代码,减少了手动编写SQL语句的工作量,提高了开发效率。
4. 增加灵活性:
- 关联关系映射框架允许进行高级查询和复杂操作,而不需要深入了解SQL的复杂性,从而提供了更大的灵活性。
5. 降低错误风险:
- 通过将关系映射到对象,可以减少手动编写SQL时可能引入的错误,如拼写错误或SQL注入,提高了代码的质量和安全性。
灵活性:
1. 一对一、一对多和多对多关系:
- MyBatis支持各种关系映射,包括一对一、一对多和多对多关系,使得能够轻松地处理不同类型的关联数据。
2. 动态SQL:
- MyBatis允许在SQL中使用动态条件,根据不同情况生成不同的SQL语句,提供了更大的灵活性。
3. 自定义查询:
- 可以使用自定义SQL查询来满足特定需求,无需受限于框架生成的SQL语句。
4. 延迟加载:
- MyBatis支持延迟加载,允许在需要时加载关联数据,提高性能并减少不必要的数据检索。
综上所述,MyBatis的关联关系映射在复杂数据模型中至关重要,因为它提供了一种更高级、更抽象的方式来处理数据库操作,同时也提供了丰富的配置和扩展选项,使开发人员能够以最灵活的方式满足不同项目的需求。它不仅提高了代码的可维护性和开发效率,还降低了错误风险,使开发人员能够更轻松地应对复杂性,是现代应用程序开发中不可或缺的工具之一。