Mybatis

Mybatis

环境:

  • JDK1.8

  • Mysql5.7

  • Maven3.6.1

  • IDEA

1、简介

1.1 什么是Mybatis

在这里插入图片描述

  • MyBatis 是一款优秀的持久层框架
  • 它支持自定义 SQL、存储过程以及高级映射。
  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
  • MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
  • MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了[google code](https://baike.baidu.com/item/google code/2346604),并且改名为MyBatis 。
  • 2013年11月迁移到Github

如何获取Mybatis?

1.2、持久化

数据持久化

  • 持久化就是将程序中的数据进行瞬时状态与持久化状态转化的过程
  • 内存:断电即失
  • 持久化的方式:数据库(JDBC),IO文件持久化等

为什么需要持久化?

  • 有些数据需要保存下来,不能丢失
  • 内存容量有限,且断电即失

1.3、持久层

Web项目的开发包括:Dao层、Service层、Controller层

  • Dao层就是持久层,完成持久化工作的代码模块
  • 层界限十分明显

1.4为什么要使用Mybatis?

  • 方便、简化开发
  • 传统的JDBC太过于复杂。框架,自动化
  • 优点:
    • 简单易学
    • 灵活
    • 解除sql与程序代码的耦合
    • 提供映射标签,支持对象与数据库的orm字段关系映射
    • 提供对象关系映射标签,支持对象关系组建维护
    • 提供xml标签,支持编写动态sql。

2、第一个Mybatis程序

​ 思路:搭建环境——>导入Mybatis——>编写代码——>测试

2.1、搭建环境

  1. 搭建数据库

    CREATE DATABASE `mybatis`;
    
    USE mybatis;
    
    CREATE TABLE user(
    	`id` int(10) PRIMARY KEY,
    	`name` VARCHAR(255) NOT NULL,
    	`password` VARCHAR(255) NOT NULL
    )ENGINE=INNODB DEFAULT CHARSET=utf8;
    
    INSERT INTO user() VALUES
    (1," lisi","li12345"),
    (2,"wangwu","wang12345"),
    (3,"zhangsan","zhang12345");
    
  2. 新建项目

    1. 新建一个普通的maven项目

    2. 删除src目录

    3. 导入maven依赖

    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
    
    

    资源扫描器插件

    <!--资源扫描插件-->
        <build>
            <resources>
                <resource>
                    <directory>src/main/java</directory>
                    <includes>
                        <include>**/*.properties</include>
                        <include>**/*.xml</include>
                    </includes>
                    <filtering>false</filtering>
                </resource>
            </resources>
        </build>
    

2.2、创建一个模块

  • 编写mybatis的配置文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    
        <environments default="development">
            <environment id="development">
                <transactionManager type="JDBC"/>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql//localhost/mybatis?useSSL=false&amp;
                        useUnicode=true&amp;characterEncoding=UTF-8"/>
                    <property name="username" value="zhangsan"/>
                    <property name="password" value="zhang12345"/>
                </dataSource>
            </environment>
        </environments>
        
        <mappers>
            <package name="com.rui.dao"/>
        </mappers>
        
    </configuration>
    
  • 编写mybatis的工具类

    public class MybatisUtils {
    
        private static SqlSessionFactory sqlSessionFactory = null;
    
        static {
            try {
                String resource = "mybatis-config.xml";
                InputStream inputStream = Resources.getResourceAsStream(resource);
                sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public static SqlSession getSqlSession(){
           return getSession(false);
        }
    
        //autoCommit,为false表示为非自动提交事务,与getSqlSession()一致。
        //默认为false,true为自动提交
         public static SqlSession getSqlSession(boolean autoCommit){
           return sqlSessionFactory.openSession();
        }
        
    }
    

2.3、编写代码

  • 实体类

    public class User {
    
        private int id;
        private String name;
        private String password;
    
        public User() {}
    
        public User(int id, String name, String password) {
            this.id = id;
            this.name = name;
            this.password = password;
        }
    
        public int getId() {
            return id;
        }
    
        public void setId(int id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public String getPassword() {
            return password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", password='" + password + '\'' +
                    '}';
        }
    }
    
  • dao接口

    public interface UserDao {
        //查询所有用户
        List<User> selectUsers();
    }
    
  • dao接口实现类由原来的UserDaoImpl转变为一个Mapper映射文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="com.bjpowernode.dao.UserDao">
    
        <select id="selectUsers" resultType="com.bjpowernode.entity.User" >
            select * from user;
        </select>
    
    </mapper>
    

2.4、测试

注意:org.apache.ibatis.binding.BindingException: Type interface com.bjpowernode.dao.UserDao is not known to the MapperRegistry.

MapperRegistry是什么?

核心配置文件中配置mappers

  • junit测试

    
    public class UserDaoTest {
    
        @Test
        public void selectUsers(){
    
            //第一步获取sqlSession对象
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            //代理对象
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            List<User> users = mapper.selectUsers();
            users.forEach(System.out::println);
            //关闭sqlSession
            sqlSession.close();
        }
    }
    

可以会遇到的问题

  1. 配置文件没有注册
  2. 绑定接口错误
  3. 方法名不对
  4. 返回值类型不对
  5. Maven资源导出问题

3、dao接口的映射文件

3.1、namespace

​ namespace中的包名要和接口名保持一致!

3.2、CRUD

  • 参数
    • id:就是接口中对应的方法名
    • resultType:sql语句执行的返回值类型
    • parameterType:参数类型
  1. 编写接口

    public interface UserDao {
    
        //查询所有用户
        List<User> selectUsers();
    
        //添加用户
        int insertUser(User user);
    
        //更新用户信息
        int updateUser(int id);
    
        //删除用户
        int delectUser(int id);
    }
    
  2. 编写对应得mapper中的sql语句

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.bjpowernode.dao.UserDao">
    
        <select id="selectUsers" resultType="com.bjpowernode.entity.User" >
            select * from user;
        </select>
    
        <insert id="insertUser" >
            insert into user() values(#{id},#{name},#{password});
        </insert>
    
        <update id="updateUser">
            update user set name="libai",password="libaibuku" where id=#{id};
        </update>
    
        <delete id="delectUser">
            delete from user where id=#{id};
        </delete>
    
    </mapper>
    
  3. 测试

    
    public class UserDaoTest {
    
        @Test
        public void selectUsers(){
    
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            List<User> users = mapper.selectUsers();
            users.forEach(System.out::println);
            sqlSession.close();
    
        }
    
        @Test
        public void insertUser(){
    
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            int i = mapper.insertUser(new User(4, "hanxin", "hanxinbuku"));
            sqlSession.commit();
            sqlSession.close();
    
        }
    
        @Test
        public void updateUser(){
    
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            int i = mapper.updateUser(4);
            sqlSession.commit();
            sqlSession.close();
    
        }
        @Test
        public void delectUser(){
    
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            UserDao mapper = sqlSession.getMapper(UserDao.class);
            int i = mapper.delectUser(4);
            sqlSession.commit();
            sqlSession.close();
    
        }
    
    }
    

    注意点:增删改需要提交事务

3.3、万能Map

对于值传递,有时只需要传递实体对象中的部分属性值。这时就可以用map进行传递

多个参数用map,或者注解!

User toMapSelectUser(Map map);
<select id="toMapSelectUser" resultType="com.bjpowernode.entity.User">
        select * from user where name = #{username} and id = #{userid};
    </select>
@Test
    public void toMapSelectUser(){

        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        HashMap<String, Object> map = new HashMap<>();
        map.put("userid", 5);
        map.put("username","韩信");
        User user = mapper.toMapSelectUser(map);
        System.out.println(user);
        sqlSession.close();

    }

map的key可以自定义,若采用对象则需要固定属性名称

3.4、模糊查询

  1. java代码执行的时候,传递通配符%%

    List<User> users = mapper.likeSelectUser1("%li%");
    
  2. 在sql拼接中使用通配符【建议使用!】

     <select id="likeSelectUser" resultType="com.bjpowernode.entity.User">
            select * from user where name like "%"#{name}"%"
     </select>
    

4、配置解析

4.1、核心配置文件

  • mybatis-config.xml

  • MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息

  • 严格按照顺序声明,否则报错

    configuration(配置)
        properties(属性)
        settings(设置)
        typeAliases(类型别名)
        typeHandlers(类型处理器)
        objectFactory(对象工厂)
        plugins(插件)
        environments(环境配置)
            environment(环境变量)
                transactionManager(事务管理器)
                dataSource(数据源)
        databaseIdProvider(数据库厂商标识)
        mappers(映射器)
    

4.2、环境配置(environments)

​ MyBatis 可以配置成适应多种环境

不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

​ 学会使用配置多套运行环境!

​ Mybatis默认的事务管理器是JDBC,连接池:POOLED

4.3、属性(properties)

​ 我们可以通过properties属性来实现引用配置文件

​ 这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的 子元素中设置

在xml文件中,规定了声明顺序

​ (properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,

​ reflectorFactory?,plugins?,environments?,databaseIdProvider?,mappers?)".

编写一个配置文件

jdbc.properties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&useUnicode=true&characterEncoding=UTF-8
username=zhangsan
password=zhang12345

在核心配置文件中映入

    <!--引入外部配置文件,第一种--> 
    <properties resource="jdbc.properties"/>   

	<!--引入外部配置文件并增加一些属性字段,第二种-->
    <properties resource="jdbc.properties">
        <property name="username" value="zhangsan"/>
        <property name="password" value="zhang12345"/>
    </properties>
  • 可以直接引入外部文件
  • 可以在其中增加一些属性字段
  • 如果两个文件有同一个字段,优先使用外部配置文件的!

4.4、类型别名(typeAliases)

  • 类型别名是为java类型设置一个较短的名称
  • 存在的意义仅在于用来减少完全限定名的冗余

第一种方式:为指定的类,设置别名

    <typeAliases>
        <typeAlias type="com.bjpowernode.entity.User" alias="user"/>        
    </typeAliases>

第二种方式:通过指定包名,为包下所有的实体类设置别名,例如:

扫描实体类的包,它的默认别名就为这个类的类名,首字母小写

	<typeAliases>
        <package name="com.bjpowernode.entity"/>
    </typeAliases>

在实体类比较少的时侯,使用第一种方式

如果实体类十分多的时候,建议使用第二种方式

第一种方式可以自定义别名,第二种不行,如果一定要修改可以采用注解的形式进行修改。

@Alias("user")
public class User {
    ...
}

mybatis内置的别名

别名映射的类型
_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator

4.5、设置(settings)

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项设置的含义、默认值等。

设置名描述有效值默认值
logPrefix指定 MyBatis 增加到日志名称的前缀。任何字符串未设置
logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING未设置
cacheEnabled全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。true | falsetrue
mapUnderscoreToCamelCase是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。true | falseFalse

4.6、映射器(mappers)

MapperRegistry:注册绑定我们的Mapper文件

方式一:使用声明映射文件位置进行绑定

<!-- 使用相对于类路径的资源引用 -->
<mappers>
    <mapper resource="com/bjpowernode/dao/UserDao.xml"/>
</mappers>

方式二:使用class文件绑定注册

<!-- 使用映射器接口实现类的完全限定类名 -->	
<mappers>
    <mapper class="com.bjpowernode.dao.UserDao"/>
</mappers>

注意点

  • 接口和他的Mapper配置文件必须同名!

  • 接口和他的Mapper配置文件必须在同一个包下!

方式三:使用扫描包进行注入绑定

	<!-- 将包内的映射器接口实现全部注册为映射器 -->
	<mappers>
        <package name="com.bjpowernode.dao"/>
    </mappers>

注意点

  • 接口和他的Mapper配置文件必须同名!
  • 接口和他的Mapper配置文件必须在同一个包下!

4.7、生命周期和作用域
在这里插入图片描述

不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。

SqlSessionFactoryBuilder:

  • 一旦创建了 SqlSessionFactory,就不再需要它了。
  • 局部变量

SqlSessionFactory:

  • 可以理解为:数据库连接池

  • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例!

  • SqlSessionFactory 的最佳作用域是应用作用域。

  • 最简单的就是使用单例模式或者静态单例模式。

SqlSession:

  • 连接数据库池的一个请求!
  • SqlSession 的实例不是线程安全的,因此是不能被共享的!
  • 最佳的作用域是请求或方法作用域。
  • 用完之后需要赶紧关闭,否则资源被占用!

5、ResultMap结果集映射

问题:解决属性名与字段名不一致的问题?

解决方法:

5.1、起别名

<select id="selectUsers" resultType="user" >
    select id naem pwd as password from user;
</select>

5.2、使用ResultMap进行映射

id 	name 	pwd
id 	name 	password
<!--结果集映射-->
<resultMap id="UserMap" type="user">
    <!--column数据库中的字段,property实体类中的实现-->
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    <result column="pwd" property="password"/>
</resultMap>

<select id="selectUsers" resultMap="UserMap" >
    select id naem pwd  from user;
</select>
  • resultMap 元素是 MyBatis 中最重要最强大的元素
  • ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
  • ResultMap最优秀的地方在于,虽然你已经对它相当了解了,但是根本就不需要显式地用到他们.(不需要映射的字段可以省略)

6、日志

6.1日志工厂

如果一个数据库操作,出现了异常,我们需要排错。日志就是最好的助手!

曾经:sout . debug

现在:日志工厂!

在这里插入图片描述

  • SLF4J
  • LOG4J (掌握)
  • LOG4J2
  • JDK_LOGGING
  • COMMONS_LOGGING
  • STDOUT_LOGGING (掌握)
  • NO_LOGGING

在Mybatis中具体使用那个一日志实现,在设置中设定!

6.2、STDOUT_LOGGING

在mybatis核心配置文件中,配置我们的日志!

配置:

<!--标准的日志工厂实现-->
<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

6.3、LOG4J

什么是Log4j?

通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
我们也可以控制每一条日志的输出格式
通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程
通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

第一步:导包

<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

第二步:log4j.properties

#将等级为DEBUG的日志信息输出到console和fiLe这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file
#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
1og4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n
#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/mybatis.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n
#日志输出级别
log4j.1ogger.org.mybatis=DEBUG
1og4j.logger.java.sq1=DEBUG
log4j.logger.java.sq1.statement=DEBUG
log4j.1ogger.java.sq1.ResultSet=DEBUG
log4j.1ogger.java.sq1.PreparedStatement=DEBUG

第三步:配置日志实现

<settings>
	<setting name="logImpl" value="LOG4J"/>
</settings>

第四步:Log4j的使用

简单使用

  1. 在要使用Log4j的类中,导入包 import org.apache.log4j.Logger;
  2. 日志对象,参数为当前类的class对象。定义为静态的(多个方法需要使用)
static Logger logger = Logger.getLogger(UserDaoTest.class);
  1. 日志级别
logger.info("info:进入了test()");
logger.debug("debug:进入了test()");
logger.error("error:进入了test()");

7、分页

为什么要分页?

分页确实有效,但它一定会加大系统的复杂度,但可否不分页呢?如果数据量少的话当然可以.但是对于企业信息系统来说数据量不会限制在一个小范围内.如果不顾一切的Select * from某个表,再将返回的数据一古脑的扔给客户,即使客户能够忍受成千上万足够让人眼花缭乱的表格式数据,繁忙的网络,紧张的服务器也会提出它们无声的抗议,甚至有时会以彻底的罢工作为终结.这个结局有点像古代为所欲为的暴君和他忍无可忍的臣民之间的故事.
  程序员不是暴君,他希望程序使生活变得更好而不是更糟.考虑到企业信息系统多是三层甚至更多层架构的事实,程序员在向客户展示数据时都应该采取分页的形式.如果他不想被抱怨淹没或是半夜被电话惊醒的话.

Limit分页

语法:

Select * from user  limit startIndex,pageSize;
Select * from user  limit pageSize;//0-pageSize

7.1、使用mybatis进行数据分页

  1. 接口

    // 数据分页
    List<User> toLimitSelectUser(@Param("startIndex") int startIndex,@Param("pageSize") int pageSize);
    
  2. mapper

    <select id="toLimitSelectUser" resultType="user">
       	select * from user limit #{startIndex},#{pageSize};
    </select>
    
  3. 测试

    @Test
    public void toLimitSelectUser(){
        //数据分页
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        List<User> users = mapper.toLimitSelectUser(0, 2);
        users.forEach(System.out::println);
        sqlSession.close();
    }
    

7.2、基于PageHelper插件分页

​ 1.maven坐标(导入依赖)

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.1.10</version>
</dependency>
  1. 加入plugin配置在mybatis-config.xml中(配置插件)
<plugins> 
	<plugin interceptor="com.github.pagehelper.PageInterceptor" />
</plugins>
  1. 使用查询语句之前调用PageHelper.startPage静态方法
@Test
public void selectUsers(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserDao mapper = sqlSession.getMapper(UserDao.class);
    PageHelper.startPage(1,2);
    List<User> users = mapper.selectUsers();
    users.forEach(System.out::println);
    sqlSession.close();
}

注意:在mapper映射文件中的SQL语句不能 “;” 结尾,会发生以下情况

select id,name,password from user; LIMIT ? 

除了PageHelper.startPage方法外,还提供了类似用法的PageHelper.offsetPage方法。在你需要进行分页的MyBatis,查询方法前调用PageHelper.startPage静态方法即可,紧跟在这个方法后的第一个MyBatis 查询方法会被进行分页

8、使用注解编程

8.1、面向接口编程

  • 大家之前都学过面向对象编程,也学习过接口,但在真正的开发中,很多时候我们会选择面向接口编程
  • 根本原因∶解耦,可拓展,提高复用,分层开发中,上层不用管具体的实现,大家都遵守共同的标准,使得开发变得容易,规范性更好
  • 在一个面向对象的系统中,系统的各种功能是由许许多多的不同对象协作完成的。在这种情况下,各个对象内部是如何实现自己的,对系统设计人员来讲就不那么重要了;
  • 而各个对象之间的协作关系则成为系统设计的关键。小到不同类之间的通信,大到各模块之间的交互,在系统设计之初都是要着重考虑的,这也是系统设计的主要工作内容。面向接口编程就是指按照这种思想来编程。

关于接口的理解

  • 接口从更深层次的理解,应是定义(规范,约束)与实现(名实分离的原则)的分离。

  • 接口的本身反映了系统设计人员对系统的抽象理解。

  • 接口应有两类:

    • 第一类是对一个个体的抽象,它可对应为一个抽象体(abstract class);
    • 第二类是对一个个体某一方面的抽象,即形成一个抽象面(interface) ;
  • 一个体有可能有多个抽象面。抽象体与抽象面是有区别的。

三个面向区别

  • 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性及方法.
  • 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现.
  • 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题.更多的体现就是对系统整体的架构

8.2、使用注解开发

  1. 注解在接口方法上声明

     //查询所有用户
    @Select("select * from user")
    List<User> toAnnotationSelectUsers();
    
  2. 需要在核心配置文件中绑定接口!

    <mappers>
         <package name="com.bjpowernode.dao"/>
    </mappers>
    
  3. 测试

    @Test
    public void toAnnotationSelectUsers(){
        //数据分页
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        UserDao mapper = sqlSession.getMapper(UserDao.class);
        List<User> users = mapper.toAnnotationSelectUsers();
        users.forEach(System.out::println);
        sqlSession.close();
    }
    

注:使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。

8.3、关于@Param()注解

  • 基本数据类型的参数或者String类型,需要加上
  • 引用类型不需要加
  • 如果只要一个基本类型,可以忽略,但是建议加上!
  • 我们在SQL中引用的就是我们这里的@Param("")中设定的属性名!

8.4、关于${}和#{}的区别

#{}:进行的是预编译,可以避免SQL注入

${}:使用的是字符串拼接,存在SQL注入的风险

9、mybatis详细的执行流程

9.1、步骤分析

获取并加载全局配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");

实例化SqlSessionFactoryBuilder对象
 SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();

调用sqlSEssionFactoryBuilder.build(inputStream)中
    创建XMLConfigBuilder对象
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    parser.parse()//解析了配置文件,返回实例化Configuration对象
最终返回SqlSessionFactory实例

调用sqlSessionFactory.openSession()中
		创建事务对象
		Transaction
		tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
		创建了执行器对象	
        Executor executor = this.configuration.newExecutor(Transaction, ExecutorType);
		创建sqlSession对象
		SqlSession var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
最终返回SqlSession实例对象

UserDao mapper = sqlSession.getMapper(UserDao.class);
在getMapper()中,通过动态代理、反射,实例化了接口对象的代理对象

执行CRUD操作

是否事务回滚

提交事务

9.2、流程图

在这里插入图片描述

10、Lombok(不推荐使用)

10.1、简介

Project Lombok is a java library that automatically plugs into your editor and build tools, spicing up your java.
Never write another getter or equals method again, with one annotation your class has a fully featured builder, Automate your logging variables, and much more.
  • java library

  • automatically plugs

  • build tools

  • with one annotation your class

10.2、使用步骤

  1. 在IDEA中下载Lombok插件,并重启IDEA

在这里插入图片描述

  1. 导入依赖

    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.12</version>
        <scope>provided</scope>
    </dependency>
    
  2. 在实体类上加注解

    @Getter and @Setter
    @FieldNameConstants
    @ToString
    @EqualsAndHashCode
    @AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor
    @Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
    @Data
    @Builder
    @SuperBuilder
    @Singular
    @Delegate
    @Value
    @Accessors
    @Wither
    @With
    @SneakyThrows
    @val
    @var
    @UtilityClass
    

    说明

    Data:无参构造、get、set、toString、hashCode、equals
    @AllArgsConstructor:有参
    @NoArgsConstructor:无参
    @EqualsAndHashCode:equals和hashCode
    @ToString:toString
    @Getter:get
    @Setter:set
    

    扭曲的审美,爱的隐患

    扭曲的审美,导致了被审视的对象处于亚健康状态。使用Lombok插件之后,我们的代码也处于“亚健康”状态。还是回归一开始的那句话:所有的源代码很多时候是用来阅读的,只有很少的时间是用来执行的。

    1.JDK版本问题

    当我想要将现有项目的JDK从Java 8升级到Java 11时,我发现Lombok不能正常工作了。于是我不得不将所有的Lombok注解从项目源代码中清除,并使用IDE自带的功能生成getter/setter,equals,hashCode,toString以及构造器等方法,你也可以使用Delombok工具完成这一过程。但这终究会消耗你很多的时间。

    2.胁迫使用

    当你的源代码中使用了Lombok,恰好你的代码又被其他的人所使用,那么依赖你代码的人,也必须安装Lombok插件(不管他们喜不喜欢),同时还要花费时间去了解Lombok注解的使用情况,如果不那么做,代码将无法正常运行。使用过Lombok之后,我发现这是一种很流氓的行为。

    3.可读性差

    Lombok隐藏了JavaBean封装的细节,如果你使用@AllArgsConstructor注解,它将提供一个巨型构造器,让外界有机会在初始化对象时修改类中所有的属性。首先,这是极其不安全的,因为类中某系属性我们是不希望被修改的;另外,如果某个类中有几十个属性存在,就会有一个包含几十个参数的构造器被Lombok注入到类中,这是不理智的行为;其次,构造器参数的顺序完全由Lombok所控制,我们并不能操控,只有当你需要调试时才发现有一个奇怪的“小强”在等着你;最后,在运行代码之前,所有JavaBean中的方法你只能想象他们长什么样子,你并不能看见。

    4.代码耦合度增加

    当你使用Lombok来编写某一个模块的代码后,其余依赖此模块的其他代码都需要引入Lombok依赖,同时还需要在IDE中安装Lombok的插件。虽然Lombok的依赖包并不大,但就因为其中一个地方使用了Lombok,其余所有的依赖方都要强制加入Lombok的Jar包,这是一种入侵式的耦合,如果再遇上JDK版本问题,这将是一场灾难。

    5.得不偿失

    使用Lombok,一时觉得很爽,但它却污染了你的代码,破坏了Java代码的完整性,可读性和安全性,同时还增加的团队的技术债务,这是一种弊大于利,得不偿失的操作。如果你确实想让自己的代码更加精炼,同时又兼顾可读性和编码效率,不妨使用主流的Scala或Kotlin这一基于JVM的语言。

    总结

    Lombok本身是一个优秀的Java代码库,它采用了一种取巧的语法糖,简化了Java的编码,为Java代码的精简提供了一种方式,但在使用此代码库时,需要了解到Lombok并非一个标准的Java库。使用Lombok,会增加团队的技术债务,降低代码的可读性,增大代码的耦合度和调式难度。虽然在一定程度上Lombok减少了样板代码的书写,但也带来了一些未知的风险。如果你正在参与一个团队项目(或大型项目),考虑到后续的升级与扩展,是否使用Lombok,请与你的团队多沟通和三思。

11、结果集映射的复杂使用

  • 多个学生对应一个老师,相应的一个老师对应一个学生

  • 对于学生而言,关联,多对一

  • 对于老师而言,集合,一对多

    搭建环境

    1. 建数据库表
    use mybatis;
    
    CREATE TABLE teacher(
    	id int(10) PRIMARY KEY,
    	name VARCHAR(10) not NULL
    
    )ENGINE=INNODB DEFAULT CHARSET=utf8
    insert into teacher() VALUE
    (1,"lisi");
    
    CREATE TABLE student(
    	id int PRIMARY key,
    	name VARCHAR(10) not NULL,
    	tid int not NULL,
    	FOREIGN KEY(tid) REFERENCES teacher (id)
    )ENGINE=INNODB DEFAULT CHARSET=utf8
    
    insert into student() VALUES
    (1,"wangwu",1),(2,"zhangsan",1),(3,"xiaoming",1),
    (4,"xiaohong",1),(5,"kangkang",1);
    
    1. 构建项目

    2. 创建实体类

      @Data
      public class Student {
          private int id;
          private String name;
          private Teacher teacher;
      }
      
      @Data
      public class Teacher {
          private int id;
          private String name;
          private List<Student> studentList;
      }
      
    3. 定义接口

    4. 编写接口对应的映射文件

    5. 绑定映射文件

    6. 测试

11.1、多对一的处理

  1. 定义接口

    //    查询所有学生,包括老师信息
    //  Select s.id,s.name,s.tid,t.name tname from student s,teacher t where s.tid = t.id
        List<Student> selectStudents();
    
  2. 编写映射文件

    第一种:按照嵌套查询处理

        <!--对于引用类型:association,集合用:collection-->
    
    	<!--第一种:按照嵌套查询处理-->
    	<resultMap id="studentTeacher" type="student">
            <result property="id" column="id"/>
            <result property="name" column="name"/>
            <association property="teacher" javaType="teacher" column="tid" select="selectTeacher"/>
        </resultMap>
    
        <select id="selectStudents" resultMap="studentTeacher">
            Select * from student
        </select>
    
        <select id="selectTeacher" resultType="teacher">
            Select id,name from teacher  where id = #{id}
        </select>
    
    
    

    第二种:按照结果嵌套查询

    <!--第二种:按照结果嵌套查询-->
    <resultMap id="studentTeacher" type="student">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <association property="teacher" javaType="teacher">
            <result column="tid" property="id"/>
            <result column="tname" property="name"/>
        </association>
    </resultMap>
    
    <select id="selectStudents1" resultMap="studentTeacher">
        Select s.id id,s.name name,s.tid tid,t.name tname from student s,teacher t where s.tid = t.id
    </select>
    
  3. 测试

    public void selectStudents(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        StudentDao mapper = sqlSession.getMapper(StudentDao.class);
        List<Student> students = mapper.selectStudents();
        students.forEach(System.out::println);
        sqlSession.close();
    }
    

回顾Mysql多对一查询方式:

  • 子表查询

  • 连表查询

11.2、一对多的处理

  1. 定义接口

    //    查询教师信息,及下面所有学生的信息
    //    select t.id tid,t.name tname,s.id sid,s.name sname from teacher t,student s where t.id=s.tid
        Teacher toIdSelectTeacher(@Param("id") int id);
    
  2. 编写映射文件

    第一种:按照嵌套查询处理

    <resultMap id="teacherStduent" type="teacher">
            <result column="tid" property="id"/>
            <result column="tname" property="name"/>
            <collection property="studentList" ofType="student" column="id" select="toStudents"/>
        </resultMap>
        <select id="toIdSelectTeacher" resultMap="teacherStduent1">
            select * from teacher  where id=#{id}
        </select>
        <select id="toStudents" resultType="student">
            select * from student where tid = #{tid}
        </select>
    

    第二种:按照结果嵌套查询

    <!--
    复杂的属性,我们需要单独处理对象: association 
    集合: collection  javaType="”指定属性的类型!  
    集合中的泛型信息,我们使用ofType获取
    -->
    <resultMap id="teacherStduent" type="teacher">
            <result column="tid" property="id"/>
            <result column="tname" property="name"/>
            <collection property="studentList" ofType="student" javaType="ArrayList">
                <result column="sid" property="id"/>
                <result column="sname" property="name"/>
            </collection>
        </resultMap>
        <select id="toIdSelectTeacher" resultMap="teacherStduent">
            select t.id tid,t.name tname,s.id sid,s.name sname from teacher t,student s where t.id=#{id} 			and t.id=s.tid
        </select>
    
  3. 测试

    @Test
        public void toIdSelectTeacher(){
            SqlSession sqlSession = MybatisUtils.getSqlSession();
            TeacherDao mapper = sqlSession.getMapper(TeacherDao.class);
            Teacher teacher = mapper.toIdSelectTeacher(1);
            System.out.println(teacher);
            sqlSession.close();
        }
    

小结

  1. 关联- association【多对一】

  2. 集合- collection【一对多】

  3. javaType & ofType

  4. JavaType用来指定实体类中属性的类型

  5. ofType 用来指定映射到List或者集合中的pojo类型,泛型中的约束类型!

注意点:

  • 保证SQL的可读性,尽量保证通俗易懂

  • 注意一对多和多对一中,属性名和字段的问题!

  • 如果问题不好排查错误,可以使用日志,建议使用Log4j

12、动态SQL

什么是动态SQL:动态SQL就是指根据不同的条件生成不同的SQL语句

动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。

如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。

if
choose (when, otherwise)
trim (where, set)
foreach

环境搭建

CREATE TABLE b1og(
    `id` varchar(50) NOT NULL COMMENT '博客id',
    `title` varchar(100) NOT NULL COMMENT '博客标题',
    `author` varchar(30) NOT NULL COMMENT '博客作者',
    `create_time` datetime NOT NULL COMMENT '创建时间',
    `views` int(30) NOT NULL COMMENT '浏览量'
)ENGINE=InnoDB DEFAULT CHARSET=utf8

if

<select id="queryBlogIF" parameterType="map" resultType="b1og">
    select * from blog where 1=1
    <if test="title != null">
    	and title = #{title}
    </if>
    <if test="author != null">
    	and author = #{author}
    </if>
</select>

choose (when, otherwise)

<select id="queryB1ogChoose" parameterType="map" resu7tType="b1og">
    select * from blog
    <where>
        <choose>
            <when test="title != null">
                title = {#ititle}
            </when>
            <when test="author != null">
                and author = #{author}
            </when>
            <otherwise>
                and views = #{views}
            </otherwise>
        </choose>
    </where>
</select>

trim(where、set)

<select>
    select * from b1og
    <where>
        <if test="title != null">
            title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </where>
</select>
<update id="updateB1og" parameterType="map">
	update blog
	<set>
		<if test="title != null">
			title = #{title},
        </if>
		<if test="author != null">
			author = #{author}
		</if>
	</set>
	where id = #{id}
</update>

所谓的动态SQL,本质还是SQL语句,只是我们可以在SQL层面,去执行一个逻辑代码

foreach

<select id="queryBTogForeach" parameterType="map" resu1tType="blog">
    select t from mybatis.blog
    <where>
        <foreach collection="ids" item="id" open="and (" close=")" separator="or">
        	id = #{id}
        </foreach>
   	</where>
</select>

collection:集合
item:集合元素变量
open:开始
close:结尾
separator:分割元素

动态SQL就是在拼接SQL语句,我们只要保证SQL的正确性,按照SQL的格式,去排列组合就可以了

建议:

  • 现在Mysql中写出完整的SQL,再对应的去修改成为我们的动态SQL实现通用即可!

SQL片段

有的时候,我们可能会将一些功能的部分抽取出来,方便复用!

  1. 使用SQL标签抽取公共的部分
<sq1 id="if-title-author">
    <if test="title !=null">
    	title = #{title}
    </if>
    <if test="author != null">
   		and author = #{author}
    </if>
</sq1>

2.	在需要使用的地方使用Include标签引用即可
<select id="queryB7ogIF" parameterType="map" resultType="blog">
    select * from blog
    <where>
    	<include refid="if-title-author"/>
    </where>
</select>

注意事项:

  • 最好基于单表来定义SQL片段!
  • 不要存在where标签

13、缓存(了解)

13.1、简介

查询:连接数据库,耗资源!
	一次查询的结果,给他暂存在一个可以直接取到的地方! -->内存:缓存

我们再次查询相同数据的时候,直接走缓存,就不用走数据库了
  1. 什么是缓存[ Cache ]?

    • 存在内存中的临时数据。

    • 将用户经常查询的数据放在缓存(内存)中,用户去 查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。

  2. 为什么使用缓存?

    • 减少和数据库的交互次数,减少系统开销,提高系统效率。
  3. 什么样的数据能使用缓存?

    • 经常查询并且不经常改变的数据。

13.2、Mybatis缓存

  • MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。

  • MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存

    • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)。

    • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。

    • 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

13.3、一级缓存

一级缓存也叫本地缓存:

  • 与数据库同一次会话期间查询到的数据会放在本地缓存中。
  • 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;

小结:一级缓存默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个区间段!

​ —级缓存就是一个Map。

13.4、二级缓存

  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;
  • 工作机制
    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
    • 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
    • 新的会话查询信息,就可以从二级缓存中获取内容;
    • 不同的mapper查出的数据会放在自己对应的缓存(map)中;

缓存失效的情况:

  1. 查询不同的东西
  2. 增删改操作,可能会改变原来的数据,所以必定会刷新缓存!
  3. 查询不同的Mapper.xml

测试:

​ 1.问题:我们需要将实体类序列化!否则会报错

小结:

  • 只要开启了二级缓存,在同一个Mapper下就有效
  • 所有的数据都会先放在一级缓存中;
  • 只有当会话提交,或者关闭的时候,才会提交到二级缓冲中!

13.5、缓存原理

  1. 先查看二级缓存中是否存在
  2. 在查看一级缓存中是否存在
  3. 查询数据库

13.6、自定义缓存-ehcache

Ehcache是一种广泛使用的开源Java分布式缓存。主要面向通用缓存

要在程序中使用ehcache,先要导包!

<!-- https : //mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.1.0</version>
</dependency>

在mapper中指定使用我们的ehcache缓存实现!

<!--在当前Mapper.xm1中使用二级缓存-->
<cache type="org.mybatis.caches.ehcache.Ehcachecache"/>

ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">
    <!--
       diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
       user.home – 用户主目录
       user.dir  – 用户当前工作目录
       java.io.tmpdir – 默认临时文件路径
     -->
    <diskStore path="java.io.tmpdir/Tmp_EhCache"/>
    
    <defaultCache
            eternal="false"
            maxElementsInMemory="10000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="259200"
            memoryStoreEvictionPolicy="LRU"/>
  
    <cache
            name="cloud_user"
            eternal="false"
            maxElementsInMemory="5000"
            overflowToDisk="false"
            diskPersistent="false"
            timeToIdleSeconds="1800"
            timeToLiveSeconds="1800"
            memoryStoreEvictionPolicy="LRU"/>
    
     <!--
       defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
     -->
    <!--
      name:缓存名称。
      maxElementsInMemory:缓存最大数目
      maxElementsOnDisk:硬盘最大缓存个数。
      eternal:对象是否永久有效,一但设置了,timeout将不起作用。
      overflowToDisk:是否保存到磁盘,当系统当机时
      timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
      timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
      diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
      diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
      diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
      memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
      clearOnFlush:内存数量最大时是否清除。
      memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
      FIFO,first in first out,这个是大家最熟的,先进先出。
      LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
      LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
   -->
    
  
</ehcache>

Redis数据库来做缓存!

扩展

建造者模式

  • 建造者模式也属于创建型模式,它提供了一种创建对象的最佳方式
  • 定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示
  • 主要作用:在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象。
  • 用户只需要给出指定复杂对象的类型和内容,建造者模式负责按顺序创建复杂对象(把内部的建造过程和细节隐藏起来)

在这里插入图片描述

角色:指挥者、图纸、工人、楼房
	
    指挥者 调用 工人 建造房屋
    工人 通过 图纸 建造房屋

代码实现:

//房屋实体类
package com.bjpowernode.deom;

public class House {

    private String A;
    private String B;
    private String c;

    public House() {
    }

    public String getA() {
        return A;
    }

    public void setA(String a) {
        A = a;
    }

    public String getB() {
        return B;
    }

    public void setB(String b) {
        B = b;
    }

    public String getC() {
        return c;
    }

    public void setC(String c) {
        this.c = c;
    }

    @Override
    public String toString() {
        return "House{" +
                "A='" + A + '\'' +
                ", B='" + B + '\'' +
                ", c='" + c + '\'' +
                '}';
    }
}

//图纸
public abstract class Builder {

    protected House house = new House();

    abstract void buildA();
    abstract void buildB();
    abstract void buildC();

    abstract House buildHouse();

}
//工人
public class Worker extends Builder {

    @Override
    public void buildA() {
        house.setA("打地基");
    }

    @Override
    public void buildB() {
        house.setB("搭建");
    }

    @Override
    public void buildC() {
        house.setC("粉刷");
    }

    @Override
    public House buildHouse() {
        buildA();
        buildB();
        buildC();
        return house;
    }
}

//指挥者
public class Director {

    private Builder worker;

    public Director() {
        worker = new Worker();
    }

    public Director(Builder worker) {
        this.worker = worker;
    }

    public House build(){
        return worker.buildHouse();
    }

}
//测试
public class Test {
    public static void main(String[] args) {
        Director director = new Director();
        House build = director.build();
        System.out.println(build);
    }
}
  • 上面示例是Builder模式的常规用法,导演类Director在Builder模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有些情况下需要简化系统结构,可以把Director和抽象建造者进行结合。
  • 通过静态内部类方式实现零件无序装配构造,这种方式使用更加灵活,更符合定义。内部有复杂对象的默认实现,使用时可以根据用户需求自由定义更改内容,并且无需改变具体的构造方式。就可以生产出不同复杂产品
  • 比如:比如麦当劳的套餐,服务员(具体建造者)可以随意搭配任意几种产品(零件)组成一款套餐(产品),然后出售给客户。比第一种方式少了指挥者,主要是因为第二种方式把指挥者交给用户来操作,使得产品的创建更加简单灵活。

通过静态内部类方式实现无序配置

public class Food {

    private String A="全家桶";
    private String B="可乐";
    private String c="汉堡";

    public Food() {
    }

    public String getA() {
        return A;
    }

    public void setA(String a) {
        A = a;
    }

    public String getB() {
        return B;
    }

    public void setB(String b) {
        B = b;
    }

    public String getC() {
        return c;
    }

    public void setC(String c) {
        this.c = c;
    }

    @Override
    public String toString() {
        return "House{" +
                "A='" + A + '\'' +
                ", B='" + B + '\'' +
                ", c='" + c + '\'' +
                '}';
    }
}
public abstract class Builder {

    protected Food food = new Food();

    abstract Builder buildA();
    abstract Builder buildB();
    abstract Builder buildC();

    abstract Food buildFood();

}
public class Worker extends Builder {

    @Override
    public Builder buildA() {
        food.setA("雪碧");
        return this;
    }

    @Override
    public Builder buildB() {
        food.setB("鸡翅");
        return this;
    }

    @Override
    public Builder buildC() {
        food.setC("薯条");
        return this;
    }

    @Override
    Food buildFood() {
        return food;
    }

}
public class Test {
    public static void main(String[] args) {
        Food build = new Worker().buildA().buildC().buildFood();
        System.out.println(build);
    }
}

链式编程

优点:
产品的建造和表示分离,实现了解耦。使用建造者模式可以使客户端不必知道产品内部组成的细节。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰
具体的建造者类之间是相互独立的,这有利于系统的扩展。增加新的具体建造者无需修改原有类库的代码,符合“开闭原则“。

缺点:
建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
Builder模式中具有很重要的作用,它用于指导具体构建者如何构建产品,控制调用先后次序,并向调用者返回完整的产品类,但是有些情况下需要简化系统结构,可以把Director和抽象建造者进行结合。

  • 通过静态内部类方式实现零件无序装配构造,这种方式使用更加灵活,更符合定义。内部有复杂对象的默认实现,使用时可以根据用户需求自由定义更改内容,并且无需改变具体的构造方式。就可以生产出不同复杂产品
  • 比如:比如麦当劳的套餐,服务员(具体建造者)可以随意搭配任意几种产品(零件)组成一款套餐(产品),然后出售给客户。比第一种方式少了指挥者,主要是因为第二种方式把指挥者交给用户来操作,使得产品的创建更加简单灵活。

通过静态内部类方式实现无序配置

public class Food {

    private String A="全家桶";
    private String B="可乐";
    private String c="汉堡";

    public Food() {
    }

    public String getA() {
        return A;
    }

    public void setA(String a) {
        A = a;
    }

    public String getB() {
        return B;
    }

    public void setB(String b) {
        B = b;
    }

    public String getC() {
        return c;
    }

    public void setC(String c) {
        this.c = c;
    }

    @Override
    public String toString() {
        return "House{" +
                "A='" + A + '\'' +
                ", B='" + B + '\'' +
                ", c='" + c + '\'' +
                '}';
    }
}
public abstract class Builder {

    protected Food food = new Food();

    abstract Builder buildA();
    abstract Builder buildB();
    abstract Builder buildC();

    abstract Food buildFood();

}
public class Worker extends Builder {

    @Override
    public Builder buildA() {
        food.setA("雪碧");
        return this;
    }

    @Override
    public Builder buildB() {
        food.setB("鸡翅");
        return this;
    }

    @Override
    public Builder buildC() {
        food.setC("薯条");
        return this;
    }

    @Override
    Food buildFood() {
        return food;
    }

}
public class Test {
    public static void main(String[] args) {
        Food build = new Worker().buildA().buildC().buildFood();
        System.out.println(build);
    }
}

链式编程

优点:
产品的建造和表示分离,实现了解耦。使用建造者模式可以使客户端不必知道产品内部组成的细节。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰
具体的建造者类之间是相互独立的,这有利于系统的扩展。增加新的具体建造者无需修改原有类库的代码,符合“开闭原则“。

缺点:
建造者模式所创建的产品一般具有较多的共同点,其组成部分相似;如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值