MySQL ORM 组件 Mysql4J

MIT
Java
跨平台
2020-03-03
小熊-Ursaminor

Mysql4J是一款用于访问MySQL数据库的ORM组件。通过它可以方便地构建sql语句,并且可在编译期进行sql语法检查,还可以把得到的数据结果转换成List、Map、Set等形式。简单易用,独辟蹊径,是提高代码编写效率的好工具。

Mysql4J适用于中小型项目。

特点

底层基于Spring的JdbcTemplate

Mysql4J底层通过Spring的JdbcTemplate对象访问数据库,该JdbcTemplate对象需要事先配置好并保证可用,然后设置到Mysql4J中。

编译期纠错

Mysql4J可在编译期检查出大部分的sql语法错误,相比于在运行时才发现sql错误,对于快速开发,有很大帮助。

由于用java模拟了sql语句,所以sql的语法,对应到了java语法,这样当sql拼写不正确时(逻辑错误除外),通常也是一个java语法错误,如果使用IDE的代码提示工具,则基本的匹配和代码构建是自动提示的。

数据结果,封装友好

Mysql4J对查询得到的数据,可进行相对灵活的后处理。可以是pojo对象的List集合,也可以是Map集合。同JdbcTemplate不同,JdbcTemplate的getMap方法,仅返回Map表示的一条数据记录,Mysql4J则把多行查询结果,放在Map中,Map的key值,可以由程序员指定,通常指定成表中某个不重复的字段。还可以方便的直接得到单个值,或者某一列值的List列表。

如果不想定义pojo对象,可以直接使用Row对象封装行记录,Row实现了Map接口,提供了getString、getInteger、getDate等方便的数据提取方法。

Api 简单易懂,上手迅速

Mysql4J的api非常简单,几乎不用阅读文档,仅根据java IDE的代码工具提示,就可以构建完整的程序代码。

SQL 即用即写,逻辑集中不分散

sql语句中,通常包含着关键的业务逻辑。Mysql4J可保证在一个java方法中,业务逻辑的完整性,sql语句不会被写到程序文件之外。打开一个类,逻辑全部在其内部展现,相对于把sql写到xml中,代码更清晰易读。

使用

Mysql4J的使用非常简单,例如:

// 表(mapping类)
um_member m = new um_member();

// 查询对象
Query query = new Query();
query.from(m);
query.where(m.level.eq(1));
query.where(m.name.startsWith("urs"));
query.where(m.nick.contains("小熊"));
query.where(m.reg_time.le(new Date(), DATETIME));
query.order(m.reg_time.desc());
query.limit(0, 20);

// 获得结果集
List list = query.getList();

其中um_member为数据库表,类名直接对应表名,也是Mysql4J的mapping类,用来配置表结构。um_member m = new um_member(),变量m,可理解为表的别名,m.level,m.name,m.nick,m.reg_time是m对象的属性名,同时也是表um_member的字段名!

这样映射后,数据库中表名和字段名的改变,只需要修改mapping类um_member,然后分布在程序各处的Mysql4J数据库访问代码,会自动报出编译期错误,方便修改,保证了表名和字段名的一致。类似报出编译期错误警告的地方还有很多,比如把数字赋值给一个字符字段,等等。

Mysql4J不仅把表和字段直接映射成java的类和属性,而且把查询、插入、修改、删除数据的操作,也做了对象化处理。这些都采用java实现并且尽量遵循sql的语法习惯!

编写mapping类

使用Mysql4J,需要配置一个mapping类。在传统的ORM组件中,这个配置通常以xml格式写在文件里,用来指明java数据模型和数据库中的表以及字段的映射关系。在Mysql4J中,这个配置就是一个java类,它不仅说明了映射关系,还能被实例化成一个对象,在数据操作中使用。

具体使用方法如下:

首先假设数据库中存在如下的表:

// 会员表
CREATE TABLE `um_member` (
   `id` bigint(20) NOT NULL AUTO_INCREMENT,
   `name` varchar(20) DEFAULT NULL,
   `nick` varchar(30) DEFAULT NULL,
   `level` smallint(6) DEFAULT NULL,
   `tel` varchar(20) DEFAULT NULL,
   `reg_time` datetime DEFAULT NULL,
   `last_login_time` datetime DEFAULT NULL,
   `login_times` int(11) DEFAULT NULL,
   PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

它的配置类则按照如下方式书写:

// 类名 = 表名:类名需要同数据库表名保持完全一致,该类必须继承Table作为父类
public class um_member extends Table
{
    // 构造方法的固定写法
    public um_member()
    {
        // 数据映射对象的class,可以为Row.class或者pojo对象的class,这里采用pojo对象
        super(Member.class);

        // 初始化,固定写法,不可省略
        super.init();

        // 获得可用的JdbcTemplate对象
        JdbcTemplate jdbcTemplate = (JdbcTemplate)YourSpring.get("jdbcTemplate");
        // 设置jdbcTemplate
        super.setJdbcTemplate(jdbcTemplate );
    }

    // 属性名 = 字段名:属性名需要同数据库字段名保持完全一致
    public Num id = new Num().asPrimaryKey().asIncrement();              // 主键ID
    public Chr name;                                                     // 姓名
    public Chr nick;                                                     // 昵称
    public Num level;                                                    // 会员等级
    public Chr tel;                                                      // 电话
    public Datetime reg_time = new Datetime().property("registerTime");  //注册时间
    public Datetime last_login_time;                                     // 最近登录时间
    public Num login_times;                                              // 登录次数
}

该类继承了cn.com.ursaminor.mysql4j.core.Table类,Table类是所有配置类的父类,配置类必须继承此类,继承该类不需要实现任何抽象方法。

配置类的类名,必须同表名相同,这可能违反了java的命名规范,但使得配置类在使用时,更像一个表,而不是一个对象。

配置类由两部分组成,一个是构造方法,一个是成员变量,不需要普通的方法。

构造方法对参数无要求,内部要求必须调用下列父类的方法:

一个是父类构造方法super(Class cls),用来指定数据查询结果的装载对象,即封装数据的pojo对象,如果没有pojo对象,可以直接使用Row,即super(Row.class)。

另一个是super.init(),该方法负责初始化工作,必须调用,不可省略。

第三个是super.setJdbcTemplate(jdbcTemplate),jdbcTemplate对象需要使用者自行获取,并保证可用。

后两个方法可以合并成super.init(jdbcTemplate)。

把jdbcTemplate绑定到配置类,可以自然地支持多数据库情况。并且在使用时,无须再设置jdbcTemplate,只需要简单实例化配置类即可:

um_member m = new um_member();

配置类中,需要配置同表字段一一对应的成员变量,成员变量的名称,必须同表的字段名保持一致,如:

public Num login_times;

这里也可能会违反java的命名规范,但这使配置类应用起来更像一个表。类名和属性名如果不同数据库的表名字段名保持一致,将导致sql拼写错误。

成员变量必须被声明为public的,并且只能是Chr、Num、Datetime三种类型之一。Chr代表该属性对应数据库的字符型字段,Num代表该属性对应数据库的数字型字段,Datetime目前只针对datetime类型字段,其它字段类型,会在后续版本扩展。

如果字段不需要其它的修饰,则不需要被赋值,仅保持声明即可,super.init()方法会自动给它们赋值一个对应的对象,不会出现属性为null的情况。如果需要其它修饰,比如设置为主键、设置为自增长、设置pojo类中的对应属性,则需要手动配置。如:

public Num id = new Num().asPrimaryKey().asIncrement();
public Datetime reg_time = new Datetime().property("registerTime");

这里,id属性被设置成主键,并且是自增长类型。reg_time属性被指定了pojo类中对应的属性为registerTime。如果pojo类中对应的属性为regTime,则不需要上述语句,super.init()会自动转化并为其赋值。

配置类可重复使用,虽然配置类是有状态的,但在使用过程中,其状态不会发生改变,可以构建一个配置类的工厂类,也可将配置类作为单例模式写到Spring中。最简单的使用方式,直接new一个就好:um_member m = new um_member();

编写pojo类

pojo类是一个普通的装载数据的对象,遵循JavaBean风格,不需要继承和实现任何父类和接口。

如果表的主键是自增长的,新增记录后的主键值,会自动设置到pojo类中,该pojo类需要有主键的setter方法。

一个表如果有对应的pojo类,则需要在配置类构造方法的super(Class cls)中设置,比如

super(Member.class);

Member类源码如下:

// 会员对象
public class Member
{
    private Long id;             // 主键ID
    private String name;         // 姓名
    private String nick;         // 昵称
    private Integer level;       // 会员等级
    private String tel;          // 电话
    private Date registerTime;   // 注册时间
    private Date lastLoginTime;  // 最近登录时间
    private Integer loginTimes;  // 登录次数

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    // 省略其它属性的getter/setter方法
}

使用Row

如果不使用自定义的pojo类装载数据,Mysql4j提供了Row类来装载数据。Row实现了Map接口,提供了getString、getInteger、getDate等方便的数据提取方法。可以作为Map的替代。

如果表的主键是自增长的,新增记录后的主键值,会自动设置到Row类中。

使用Query查询数据

Query类是Mysql4J的查询类,通过它可以方便的构建大多数的select语句。并且方便地得到想要的数据结果。

A:例1

// 值
int level = 2;
String name = "ur";
String nick = "小熊";
Date date = new Date(System.currentTimeMillis()-7*24*60*60*1000);

// 表
um_member m = new um_member();

// 构建sql
Query query = new Query();
query.from(m);                                // from um_member m
query.where(m.level.eq(level));               // m.level=2
query.where(m.name.startsWith(name));         // m.name like 'ur%'
query.where(m.nick.contains(nick))            // m.nick like '%小熊%' 
     .nullIgnore();                           // 如果String变量nick为空,则忽略m.nick like...
query.where(m.reg_time.ge(date, DATETIME));   // m.reg_time>='2020-01-02 17:42:09'
query.order(m.reg_time.desc());               // order by m.reg_time desc
query.limit(0, 20);                           // limit 0,20

// 执行
List<Member> list = query.getList(Member.class);

上述语句生成sql为:

select t0.id,t0.name,t0.nick,t0.level,t0.tel,t0.reg_time,t0.last_login_time,t0.login_times
from um_member t0
where t0.level=2 and t0.name like 'ur%' and t0.nick like '%小熊%' 
      and t0.reg_time>='2020-01-02 17:42:09'
order by t0.reg_time desc limit 0,20

表别名实际为t0,同变量名m不一致,不会导致任何问题。下同。

B:例2

// 值
int level = 2;
Date date = new Date(System.currentTimeMillis()-7*24*60*60*1000);

// 表
um_member m = new um_member();
um_order_form of = new um_order_form();

// query支持链式方法调用
Query query = new Query()
query.from(m)                                // from um_member m
     .from(of, m.id.eq(of.m_id))             // , um_order_form of where m.id=of.m_id
     .select(m.name)                         // select m.name
     .select(Fun.sum(of.count).as("count"))  // sum(of.count) as count
     .where(m.level.eq(level))               // m.level=2
     .where(m.reg_time.ge(date, DATETIME))   // m.reg_time>='2020-01-02 17:42:09'
     .having(Fun.sum(of.count).ge(5))        // having sum(of.count)>=5
     .group(m.name);                         // group by m.name

// 执行
List<Member> list =  query.getList(Member.class);

上述语句生成sql为:

select t2.name,sum(t3.count) as count
from um_member t2,um_order_form t3
where t2.id=t3.m_id and t2.level=2 and t2.reg_time>='2020-01-02 17:42:09'
group by t2.name having sum(t3.count)>=5

C:例3

// 表
um_member m = new um_member();
um_order_form of = new um_order_form();

// 子查询
Query sub = new Query()
                .from(of)                          // from um_order_form of
                .select(of.m_id)                   // select of.m_id
                .having(Fun.sum(of.count).ge(5))   // having sum(of.count)>=5
                .group(of.m_id);                   // group by of.m_id

// 构建sql
Query query = new Query();
query.from(m);                       // from um_member m
query.where(m.id.in(sub));           // where m.id in ( ... )

// 执行
List<Member> list =  query.getList(Member.class);

上述语句生成sql为:

select t5.id,t5.name,t5.nick,t5.level,t5.tel,t5.reg_time,t5.last_login_time,t5.login_times
from um_member t5 where t5.id in 
(select t6.m_id from um_order_form t6 group by t6.m_id having sum(t6.count)>=5)

得到特定结构的查询结果

Query对象返回的数据结果,可以有多种形式。

方法 说明
List<R> getList() 返回以List形式表示的结果集,元素类型<R>为配置类中指定的class,List的元素可以为pojo对象或者Row对象
Integer getCount() 返回忽略分页后的结果行数,用于分页查询中
V getValue(Class<V> valueClass) 返回一个值,值的类型由valueClass指定,比如String.class、Integer.class。当数据结果有多条记录时,仅返回第一条记录;当没有匹配结果时,返回null。该方法要求select关键字指定且仅指定一列。
List<V> getValues(Class<V> valueClass) 返回值的List,元素类型由valueClass指定,比如String.class、Integer.class。当没有匹配结果时,返回空的List对象。该方法要求select关键字指定且仅指定一列。
Set<V> getSet(Class<V> valueClass) 返回值的Set,元素类型由valueClass指定,比如String.class、Integer.class。当没有匹配结果时,返回空的Set对象。该方法要求select关键字指定且仅指定一列。
R getOne() 返回一条记录,数据类型<R>为配置类中指定的class,List的元素可以为pojo对象或者Row对象。当没有匹配结果时,返回null。
Map<Object,R> getMap(String property) 返回以Map形式表示的结果集,需要指定key,即pojo的property。Value的类型<R>为配置类中指定的class。value可以为pojo对象或者Row对象。当没有匹配结果时,返回空的Map对象。

Map<K,R> getMap(KeyCallback<K,R> keyCallback)

返回以Map形式表示的结果集,通过回调获得属性值作为key。Value的类型<R>为配置类中指定的class。value可以为pojo对象或者Row对象。当没有匹配结果时,返回空的Map对象。

Map<K,V> getMap(KeyValueCallback<K,V,R> keyValueCallback)

返回以Map形式表示的结果集。<R>指定了最初返回数据的类型,为配置类中指定的class。该方法通过回调获得属性值作为key和value。当没有匹配结果时,返回空的Map对象。

 

 

 

 

 

 

 

 

使用Inserter插入数据

如果要在表中插入新数据,可使用Inserter类,Inserter类用来构建insert语句并提交。

举例:

A:使用值创建一个insert语句

// 表
um_member m = new um_member();

// 值
String name = "ursaminor";
String nick = "小熊";
Integer level = 1;
String tel = "1234567890";
Date registerTime = new Date();
Date lastLoginTime = null;
Integer loginTimes = 0;

// 构建sql
Inserter inserter = new Inserter(m);             // insert into um_member
inserter.set(m.name, name);                      // name,            // 'ursaminor'
inserter.set(m.nick, nick);                      // nick,            // '小熊'
inserter.set(m.level, level);                    // level,           // 1
inserter.set(m.tel, tel);                        // tel,             // '1234567890'
inserter.set(m.reg_time, registerTime);          // reg_time,        // '2020-01-02 17:42:09'
inserter.set(m.last_login_time, lastLoginTime);  // last_login_time, // null
inserter.set(m.login_times, loginTimes);         // login_times,     // 0

// 提交
inserter.execute();

上述语句生成sql为:

insert into um_member (name,nick,level,tel,reg_time,last_login_time,login_times)
values ('ursaminor','小熊',1,'1234567890','2020-01-09 17:42:09',null,0)

B:使用pojo/Row对象创建一个insert语句

// 表
um_member m = new um_member();

// pojo对象
Member member = new Member();
member.setName("ursaminor");
member.setNick("小熊");
member.setLevel(1);
member.setTel("1234567890");
member.setRegisterTime(new Date());

// 提交
Inserter.insert(m, member);

上述语句生成sql为:

insert into um_member (name,nick,level,tel,reg_time,last_login_time,login_times)
values ('ursaminor','小熊',1,'1234567890','2020-01-09 17:42:09',null,null)

C:Inseter还可以生成 insert into...select...语句

// 表
um_member m = new um_member();
um_order_form of = new um_order_form();

// 值
int level = 2;
Date date = new Date(System.currentTimeMillis()-7*24*60*60*1000);

// Query对象,用来生成select语句
Query query = new Query();
query.from(m);                                  // from um_member m
query.select(m.id.as(of.m_id));                 // select m.id as m_id
query.select(Fun.eval("1").as(of.count));       // 1 as count
query.select(Fun.eval("8.00").as(of.price));    // 8.00 as price
query.where(m.level.ge(level));                 // where m.level>=2
query.where(m.reg_time.ge(date, DATETIME));     // m.reg_time>='2020-01-02 17:42:09'
      
// 构建sql
Inserter inserter = new Inserter(of);            // insert into um_order_form
inserter.columns(of.m_id, of.count, of.price);   // (m_id, count, price)
inserter.with(query);                            // select ...

// 执行
inserter.execute();

上述语句生成sql为:

insert into um_order_form (m_id,count,price)
    select t11.id as m_id,1 as count,8.00 as price from um_member t11
    where t11.level>=2 and t11.reg_time>='2020-01-02 17:42:09'

如果表的主键是自增长的,新增记录后的主键值,会自动设置到pojo类中,该pojo类需要有主键的setter方法。

如果没有使用pojo类,而是使用Row类型,则主键值也会自动设置。

使用Updater修改数据

如果要修改表中的数据,可使用Updater类,Updater类用来构建update语句并提交。

举例:

A:使用值创建一个update语句

// 表
um_member m = new um_member();

// 要修改的值
Long id = 2020L;
String nick = "小熊";
Integer level = 1;
String tel = "1234567890";
Date lastLoginTime = new Date();
Integer loginTimes = 1;

// 构建sql
Updater updater = new Updater(m);              // update um_member m
updater.set(m.nick, nick);                     // set m.nick='小熊'
updater.set(m.level, level);                   // m.level=1
updater.set(m.tel, tel);                       // m.tel='1234567890'
updater.set(m.last_login_time,lastLoginTime);  // m.last_login_time='2020-01-09 17:42:09'
updater.set(m.login_times, loginTimes);        // m.login_times=1
updater.where(m.id.eq(id));                    // where m.id=2020

// 执行
updater.execute();

上述语句生成sql为:

update um_member t14
set t14.nick='小熊',t14.level=1,t14.tel='1234567890',
t14.last_login_time='2020-01-09 17:42:09',t14.login_times=1
where t14.id=2020

B:使用pojo/Row对象创建一个update语句

// 表
um_member m = new um_member();

// pojo类
Member member = new Member();
member.setId(2020L);
member.setName("ursaminor");
member.setNick("小熊");
member.setLevel(1);

// 执行
Updater.update(m, member);

上述语句生成sql为:

update um_member
set name='ursaminor',nick='小熊',level=1,tel=null,reg_time=null,last_login_time=null,login_times=null
where id=2020

C:Updater还可以生成带有select子句的update语句

// 表
um_member m = new um_member();
um_order_form of = new um_order_form();

// 临时表
class TempTable extends Query
{
    public Num id;
}

// 临时表就是一个Query对象
TempTable tt = new TempTable();
tt.from(of);                           // from um_order_form of
tt.select(of.m_id.as(tt.id));          // select of.m_id as id
tt.group(of.m_id);                     // group by of.m_id
tt.having(Fun.count(of.id).ge(10));    // having count(of.id)>=10

// 构建sql
Updater updater = new Updater(m);      // update um_member m
updater.with(tt, tt.id.eq(m.id));      // (select ... ) tt  where tt.id=m.id
updater.set(m.level, 2);               // set m.level=2

// 执行
updater.execute();

上述语句生成sql为:

update um_member t16,
(select t17.m_id as id from um_order_form t17 group by t17.m_id having count(t17.id)>=10) t18
set t16.level=2
where t18.id=t16.id

使用Deleter删除数据

如果要删除表中的数据,可使用Deteler类,Deleter类用来构建delete语句并提交。

举例:

A:使用值创建一个delete语句

// 表
um_member m = new um_member(); 

// 值
Integer level = 1;

// 构建sql
Deleter deleter = new Deleter(m);         // delete m from um_member m
deleter.where(m.level.eq(level));         // where m.level=1

// 提交
deleter.execute();

上述语句生成sql为:

delete t19 from um_member t19 where t19.level=1

B:使用pojo/Row对象创建一个delete语句

// 表
um_member m = new um_member(); 

// pojo对象
Member member = new Member();
member.setId(2020L);

// 提交
Deleter.delete(m, member);

上述语句生成sql为:

delete from um_member where id=2020

C:Deleter还可以生成带有select子句的delete语句

// 表
um_member m = new um_member();
um_order_form of = new um_order_form();

// 临时表
class Tm extends Query
{
    public Num id;
}

// 临时表就是一个Query对象
Tm s = new Tm();
s.from(of);                             // from um_order_form of
s.select(of.m_id.as(s.id));             // select of.m_id as id
s.group(of.m_id);                       // group by of.m_id
s.having(Fun.count(of.id).ge(10));      // having count(of.id)>=10

// 构建sql
Deleter deleter = new Deleter(m);       // delete m from um_member m
deleter.with(s, s.id.eq(m.id));         // (select ... ) s where s.id=m.id

// 执行
deleter.execute();

上述语句生成sql为:

delete t21
from um_member t21,
    (select t22.m_id as id from um_order_form t22 group by t22.m_id having count(t22.id)>=10) t23
where t23.id=t21.id
加载中

暂无资讯

暂无问答

暂无博客

返回顶部
顶部