MyBatisPlus框架
相关概念
概念:MyBatis-Plus(简称 MP)是一个MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
特性:
- 无侵入:只做增强不做改变,不会对现有工程产生影响
- 强大的 CRUD 操作:内置通用BaseMapper,少量配置即可实现单表CRUD 操作(如果只做单表增删查改无需写任何sql)
- 支持 Lambda(避免编写查询条件时字段写错)
- 支持主键自动生成
- 内置分页插件
依赖配置
<!--使用上springboot都必须要继承父模块-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.2</version>
<relativePath/>
</parent>
<dependencies>
<!-- spring整合test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- mybatis-plus的驱动包 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.2</version>
</dependency>
<!-- 连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.23</version>
</dependency>
<!-- mysql要选择正确版本的驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql:///mybatisplus_db?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false
# MyBatisPlus配置基本与MyBatis相似
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
map-underscore-to-camel-case: true
type-aliases-package: com.lxl.pojo
Service层CRUD接口
//service层接口可以继承IService接口,IService的使用(需要另外两个接口baseMapper和ServiceImpl的配合。
@Mapper
public interface extends BaseMapper<User> {
}
//rvice接口继承Iservice。
public interface IUserService extends IService<User> {
}
//service接口的实现类继承ServiceImpl<继承basemapper的mapper,实体类>,实现IUserService接口。
@Service
public class IUserServiceImpl extends ServiceImpl<UserMapper, User> implements IU【serService {
}
save
// 插入一条记录(选择字段,策略插入)
boolean save(T entity);
// 插入(批量)
boolean saveBatch(Collection<T> entityList);
// 插入(批量)
boolean saveBatch(Collection<T> entityList, int batchSize);
saveOrUpdate
// TableId 注解存在更新记录,否插入一条记录
boolean saveOrUpdate(T entity);
// 根据updateWrapper尝试更新,否继续执行saveOrUpdate(T)方法
boolean saveOrUpdate(T entity, Wrapper<T> updateWrapper);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList);
// 批量修改插入
boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize);
remove
// 根据 entity 条件,删除记录
boolean remove(Wrapper<T> queryWrapper);
// 根据 ID 删除
boolean removeById(Serializable id);
// 根据 columnMap 条件,删除记录
boolean removeByMap(Map<String, Object> columnMap);
// 删除(根据ID 批量删除)
boolean removeByIds(Collection<? extends Serializable> idList);
update
// 根据 UpdateWrapper 条件,更新记录 需要设置sqlset
boolean update(Wrapper<T> updateWrapper);
// 根据 whereWrapper 条件,更新记录
boolean update(T updateEntity, Wrapper<T> whereWrapper);
// 根据 ID 选择修改
boolean updateById(T entity);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList);
// 根据ID 批量更新
boolean updateBatchById(Collection<T> entityList, int batchSize);
get
// 根据 ID 查询
T getById(Serializable id);
// 根据 Wrapper,查询一条记录。结果集,如果是多个会抛出异常,随机取一条加上限制条件 wrapper.last("LIMIT 1")
T getOne(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
T getOne(Wrapper<T> queryWrapper, boolean throwEx);
// 根据 Wrapper,查询一条记录
Map<String, Object> getMap(Wrapper<T> queryWrapper);
// 根据 Wrapper,查询一条记录
<V> V getObj(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
list
// 查询所有
List<T> list();
// 查询列表
List<T> list(Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
Collection<T> listByIds(Collection<? extends Serializable> idList);
// 查询(根据 columnMap 条件)
Collection<T> listByMap(Map<String, Object> columnMap);
// 查询所有列表
List<Map<String, Object>> listMaps();
// 查询列表
List<Map<String, Object>> listMaps(Wrapper<T> queryWrapper);
// 查询全部记录
List<Object> listObjs();
// 查询全部记录
<V> List<V> listObjs(Function<? super Object, V> mapper);
// 根据 Wrapper 条件,查询全部记录
List<Object> listObjs(Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录
<V> List<V> listObjs(Wrapper<T> queryWrapper, Function<? super Object, V> mapper);
// 该方法用于取出通过MybatisPlus查询到的数据并放入List中,其中取出的数据并不包括对象所有的字段,最多只能返回一个字段。
// 第一种情况,查询主键。此时会将查询到的所有主键id放入list中。
// 第二种情况,select *。默认查询数据库的主键,查询结果为主键(id),并将其放入list中。
// 第三种情况,查询非主键的其他字段,将查询字段放入list中。
// 第四种情况,查询多个字段,默认查询传入的第一个字段,并将第一个字段的查询结果放入list中。
page
// 无条件分页查询
IPage<T> page(IPage<T> page);
// 条件分页查询
IPage<T> page(IPage<T> page, Wrapper<T> queryWrapper);
// 无条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page);
// 条件分页查询
IPage<Map<String, Object>> pageMaps(IPage<T> page, Wrapper<T> queryWrapper);
count
// 查询总记录数
int count();
// 根据 Wrapper 条件,查询总记录数
int count(Wrapper<T> queryWrapper);
Chain链式
// 链式查询 普通
QueryChainWrapper<T> query();
// 链式查询 lambda 式。注意:不支持 Kotlin
LambdaQueryChainWrapper<T> lambdaQuery();
// 示例:
query().eq("column", value).one();
lambdaQuery().eq(Entity::getId, value).list();
// 链式更改 普通
UpdateChainWrapper<T> update();
// 链式更改 lambda 式。注意:不支持 Kotlin
LambdaUpdateChainWrapper<T> lambdaUpdate();
// 示例:
update().eq("column", value).remove();
lambdaUpdate().eq(Entity::getId, value).update(entity);
Mapper层CRUD接口
@Mapper
public interface UserMapper extends BaseMapper<User> {
}
INSERT
// 插入一条记录
int insert(T entity);
DELETE
// 根据 entity 条件,删除记录
int delete(@Param(Constants.WRAPPER) Wrapper<T> wrapper);
// 删除(根据ID 批量删除)
int deleteBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 ID 删除
int deleteById(Serializable id);
// 根据 columnMap 条件,删除记录
int deleteByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
UPDATE
// 根据 whereWrapper 条件,更新记录
int update(@Param(Constants.ENTITY) T updateEntity, @Param(Constants.WRAPPER) Wrapper<T> whereWrapper);
// 根据 ID 修改
int updateById(@Param(Constants.ENTITY) T entity);
SELECT
// 根据 ID 查询
T selectById(Serializable id);
// 根据 entity 条件,查询一条记录
T selectOne(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询(根据ID 批量查询)
List<T> selectBatchIds(@Param(Constants.COLLECTION) Collection<? extends Serializable> idList);
// 根据 entity 条件,查询全部记录
List<T> selectList(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 查询(根据 columnMap 条件)
List<T> selectByMap(@Param(Constants.COLUMN_MAP) Map<String, Object> columnMap);
// 根据 Wrapper 条件,查询全部记录
List<Map<String, Object>> selectMaps(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录。注意: 只返回第一个字段的值
List<Object> selectObjs(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 entity 条件,查询全部记录(并翻页)
IPage<T> selectPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询全部记录(并翻页)
IPage<Map<String, Object>> selectMapsPage(IPage<T> page, @Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
// 根据 Wrapper 条件,查询总记录数
Integer selectCount(@Param(Constants.WRAPPER) Wrapper<T> queryWrapper);
分页查询操作
/*
* 如果需要使用到mybatis-plus的分页功能,必须存在一个配置类
* 该配置类创建Mybatis的拦截器,这个拦截器的作用就是在你执行selectPage的方法的时候
* 对sql进行拦截,然后拼接limit语句实现分页。
*/
@Configuration
public class MybatisPlusConfig {
//@Bean注解:添加MybatisPlusInterceptor对象到容器中
@Bean
public MybatisPlusInterceptor createMybatisPlusInterceptor(){
//创建Mybatisplus拦截器
MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();
//往拦截器中添加分页拦截器
mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
//返回
return mybatisPlusInterceptor;
}
}
//设置当前页与页面大小
Page<User> page =new Page<>(1,2);
//当前页1,页面大小是2
page = userMapper.selectByAge(page,10);
自定义查询分页
//自定义查询语句与MP内置分页结合的要求
//1. 方法的参数必须为Page<T>, MP会解析里面的当前页与每页大小。
//2. 方法的返回值必须是Page<T>, MP会将执行结果分页数据封装返回(通过返回值封装返回)。
@Select("select * from user where age>#{age}")
public Page<User> selectByAge(Page<User> page, @Param("age") Integer age);
查询控制
Wapper实现类
//创建查询条件封装对象,使用字符串参数指定字段,可以指定泛型
QueryWrapper<User> wrapper = new QueryWrapper();
//创建lambda查询包装器,支持泛型
//使用lambda参数,相当于调用 user -> user.getAge()方法,获取列名
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper();
等/不等式查询
//age < 10
wrapper.le(User::getAge, 10);
//age > 18
wrapper.gt(User::getAge, 18);
//age = 10
wrapper.eq(User::getAge, 10);
//age != 10
wrapper.ne(User::getAge, 10);
//age > 10 && age < 30
wrapper.between(User::getAge, 10, 30);
//!(age > 10 && age < 30)
wrapper.notBetween(User::getAge, 10, 30)
and-与关系
//支持链式写法,age小于30并且大于10
wrapper.lt(User::getAge, 30).gt(User::getAge, 10);
//SELECT id,name,gender,password,age,tel FROM user
//WHERE (age < ? AND age > ?)
or-或关系
//或者关系:小于10岁或者大于30岁
wrapper.lt(User::getAge, 10).or().gt(User::getAge, 30);
//SELECT id,name,gender,password,age,tel FROM user
//WHERE (age < ? OR age > ?)
参数为空-condition
if (minAge != null) {
wrapper.gt(User::getAge, minAge); //大于
}
if (maxAge != null) {
wrapper.lt(User::getAge, maxAge); //小于
}
//或者根据内置表达式,判断是否执行方法逻辑
wrapper.gt(minAge != null, User::getAge, minAge);
wrapper.lt(maxAge != null, User::getAge, maxAge);
select-查询字段
//查询所有用户,只显示id, name, age三个属性,不是全部列
wrapper.select(User::getId, User::getName, User::getAge);
//SELECT id,name,age FROM user
group-分组查询
//查询2列:人数, 性别。 将聚合函数定义别名做为Map中的键
wrapper.select("count(*) as count, gender");
//按sex分组
wrapper.groupBy("gender");
//这里的查询方法使用selectMaps
List<Map<String, Object>> list = userMapper.selectMaps(wrapper);
//map对应一个sex = count,key = sex,value = count
like-模糊查询
//select * from user where name like '张%'
lambdaQueryWrapper.likeRight(User::getName,"张");
//select * from user where name like '%张'
lambdaQueryWrapper.likeLeft(User::getName,"张");
//select * from user where name like '%张%'
lambdaQueryWrapper.like(User::getName,"张");
//select * from user where name not like '%张%'
lambdaQueryWrapper.notLike(User::getName,"张");
order-排序查询
//SELECT id,name,gender,password,age,tel FROM user ORDER BY age DESC
lambdaQueryWrapper.orderByDesc(User::getAge);
//SELECT id,name,gender,password,age,tel FROM user ORDER BY age ASC
lambdaQueryWrapper.orderByAsc(User::getAge)
//SELECT id,name,gender,password,age,tel FROM user ORDER BY age ASC
lambdaQueryWrapper.orderBy(true, true, User::getAge)
//第二参数为false时,排序规则为desc
in-列举查询
ArrayList<Integer> list = new ArrayList()
list.add(16);
list.add(18);
lambdaQueryWrapper.in(User::getAge, list);
SELECT id,name,gender,password,age,tel FROM user WHERE age IN(16,18)
limit-分页查询
//last()方法的说明:无视优化规则直接拼接到sql语句的最后(有sql注入的风险,请谨慎使用),注意只能调用一次,多次调用以最后一次为准
lambdaQueryWrapper.last("limit 3");
//select id,name,gender,password,age,tel from user limit 3;
字段为空-判断
//select id,name,gender,password,age,tel from user age is null;
lambdaQueryWrapper.isNull(User::getAge)
//select id,name,gender,password,age,tel from user age is not null;
lambdaQueryWrapper.isNotNull(User::getAge)
常用注解
@TableName
概述:解决表名与实体类名不同问题。
例子:
@Data @TableName("tbl_user") public class User { }
@TableId
概述:标注表的主键字段对应属性,并指定自动配置ID策略。
例子:
public class User { @TableId(value="id", type=TdType.ASSIGN_ID) private Long id; }
主键生成策略:
策略 描述 AUTO 数据库 ID 自增 NONE 无状态,该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT) INPUT insert 前自行 set 主键值 ASSIGN_ID 分配 ID(主键类型为 Number(Long 和 Integer)或 String)(since 3.3.0),使用接口IdentifierGenerator的方法nextId(默认实现类为DefaultIdentifierGenerator雪花算法) ASSIGN_UUID 分配 UUID,主键类型为 String(since 3.3.0),使用接口IdentifierGenerator的方法nextUUID(默认)
@TableField
概述:字段注解(非主键),可控制被标注属性的字段映射、是否参与查询等。
例子:
public class User { //password属性对应表中字段名为pwd @TableField("pwd") private String password; //设置属性在表中是否存在映射字段 @TableField(exist = false) private Interg online; //设置该字段不参与查询 @TableField(select = false) private String phone; }
全局配置
mybatis-plus:
global-config:
db-config:
id-type: assign_id
# 让所有表主键生成策略相同
table-prefix: tbl_
# 在每个实体类的前面添加相同的前缀
逻辑删除
逻辑删除:为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,数据保留在数据库中。
- 设置逻辑删除字段。
public class User {
@TableId(value="id", type=TdType.ASSIGN_ID)
private Long id;
//逻辑删除字段,标记当前记录是否被删除
@TableLogic
private Integer deleted;
}
- 配置逻辑删除的实现。
mybatis-plus:
global-config:
db-config:
# 逻辑删除字段名
logic-delete-field: deleted
# 逻辑删除字面值:未删除为0
logic-not-delete-value: 0
# 逻辑删除字面值:删除为1
logic-delete-value: 1
- 执行逻辑删除操作。
@Test
void testLogicDeleted() {
int row = UserMapper.deleteById(5);
}
//逻辑删除的本质其实是修改操作。
//如果加了逻辑删除字段,查询数据时也会自动带上逻辑删除字段。
MP乐观锁
理论原理
概念:乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,乐观锁适用于读多写少的场景,这样可以提高程序的吞吐量。乐观锁采取了更加宽松的加锁机制。也是为了避免数据库幻读、业务处理时间过长等原因引起数据处理错误的一种机制,但乐观锁不会刻意使用数据库本身的锁机制,而是依据数据本身来保证数据的正确性。乐观锁本质是利用多线程进行修改(一个线程修改失败就需要不断循环尝试修改,可能会导致饥饿线程),如果修改的操作多就会导致线程也多,更多的线程不断循环运行,这样会导致cpu负载过高和性别变慢,所以乐观锁适合读多写少的情况。
CAS实现:java.util.concurrent.atomic包下面的原子变量使用了乐观锁的一种CAS实现方式。
MP实现乐观锁:一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数。当数据被修改时,version值会+1。当线程A要更新数据时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值与当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
实现步骤
- 数据库表中添加锁标记version字段。
ALTER TABLE tbl_user ADD COLUMN `version` INT DEFAULT 0;
- 实体类中添加对应字段,并设定当前字段为版本控制字段。
public class User {
private Long id;
@Version
private Integer version;
}
- 配置乐观锁拦截器实现锁机制对应的动态SQL语句拼装。
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
//1.定义Mp拦截器
MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
//2.添加乐观锁拦截器
mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return mpInterceptor;
}
}
- 使用乐观锁机制在修改前必须先获取到对应数据的version方可正常进行
@Test
public void testUpdateOne() {
//1.先通过要修改的数据id将当前数据查询出来
User user = userMapper.selectById(2L);
//2.将要修改的属性逐一设置进去
user.setName("lxl");
userMapper.updateById(user);
}
代码生成器
概述:AutoGenerator是MyBatis-Plus的代码生成器,通过AutoGenerator可以快速生成Entity、Mapper、Mapper XML、Service、Controller等各个模块的代码,极大的提升了开发效率。
实现步骤
- 创建Maven项目,创建表。
- 导入Maven依赖。
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.6.2</version>
</parent>
<dependencies>
<!--代码生成器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.0</version>
</dependency>
<!--模块引擎-->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.30</version>
</dependency>
<!--下面是生成后的代码需要用到的依赖-->
<!--web启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis启动器-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--druid的启动器-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.23</version>
</dependency>
<!--JUNIT单元测试启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
- 创建test包,复制如下代码到包下。
// 演示例子,执行 main 方法控制台输入模块表名回车自动生成对应项目目录中
public class CodeGenerator {
public static String scanner(String tip) {
Scanner scanner = new Scanner(System.in);
StringBuilder help = new StringBuilder();
help.append("请输入" + tip + ":");
System.out.println(help.toString());
if (scanner.hasNext()) {
String ipt = scanner.next();
if (StringUtils.isNotBlank(ipt)) {
return ipt;
}
}
throw new MybatisPlusException("请输入正确的" + tip + "!");
}
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
String moduleName = scanner("请代码存储的模块名");
gc.setOutputDir(projectPath + "/"+moduleName+"/src/main/java");
//代码的作者
gc.setAuthor("lxl");
gc.setOpen(false);
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl("jdbc:mysql:///mybatisplus_db?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false");
// dsc.setSchemaName("public");
dsc.setDriverName("com.mysql.cj.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("root");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(scanner("功能模块名"));
//设置父级包名 com.itheima.user com.itheima.teacher
pc.setParent("com.itheima");
mpg.setPackageInfo(pc);
// 自定义配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
}
};
// 如果模板引擎是 freemarker
String templatePath = "/templates/mapper.xml.ftl";
// 自定义输出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定义配置会被优先输出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
//自定义输出文件名,如果你Entity设置了前后缀、此处注意xml的名称会跟着发生变化!
return projectPath + "/"
+ moduleName +"/src/main/resources/mapper/" + pc.getModuleName()
+ "/" + tableInfo.getEntityName() + "Mapper"
+ StringPool.DOT_XML;
}
});
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 配置模板
TemplateConfig templateConfig = new TemplateConfig();
templateConfig.setXml(null);
mpg.setTemplate(templateConfig);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
// strategy.setSuperEntityClass("你自己的父类实体,没有就不用设置!");
strategy.setEntityLombokModel(true);
strategy.setRestControllerStyle(true);
// 公共父类
// strategy.setSuperControllerClass("你自己的父类控制器,没有就不用设置!");
// 写于父类中的公共字段
// strategy.setSuperEntityColumns("id");
strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
strategy.setControllerMappingHyphenStyle(true);
String preName = scanner("请输入表前缀名");
strategy.setTablePrefix(preName); // 设置表前缀
mpg.setStrategy(strategy);
mpg.setTemplateEngine(new FreemarkerTemplateEngine());
// 执行
mpg.execute();
}
}