sqlalchemy是python的第三方库,提供了SQL工具和对象关系映射(ORM)工具,类似的还有Django ORM等。
比如有个学生类stduent,属性和方法如图所示
可以在IntelliJ IDEA中,通过快捷键alt+insert生成对应的方法。
java的ORM框架,比如Mybatis等,假设现在有张stduent表,定义如下
create table student(
id int primary key auto_increment not null comment '学号',
name varchar(16) not null comment '姓名',
age int not null comment '年龄'
)
如果要通过Mybatis实现CRUD操作,显然需要实体类student,笔者举得例子比较简单,如果表的属性比较多或者表很多,每加入个表都需要实体类,写起来就会繁琐。因此,笔者想法是通过sqlalchemy读取表,然后生成实体类,或者说生成Studnet.java文件。
以student表为例。表中id的类型为int,转换成实体类则id的类型变成Integer或者int。其他也是类似的,而且实体类的实现很相似,先写属性,然后是get方法、set方法,构造函数、toString方法的实现。
既然如此,python可以通过字符串的拼接实现,但可能更繁杂,因此,笔者决定使用模板语法,使用jinja2,当然还有其他模板语法,比如Djang Template Lanage等。jinja2也是python的第三方库。
Jinja是一个基于Python设计语言的“全功能模板引擎”,下面简单的使用一下jinja2
from jinja2 import Template
template = Template('hello {{text}}')
result=template.render(text='jinja2')
print(result)
结果如下。
具体可参考jinja2的官方文件。
欢迎来到 Jinja2 — Jinja2 2.7 documentation (jinkan.org)
写之前先创建一个目录,目录名叫python_sql_java,其中创建一个settings.py文件。代码如下,
from sqlalchemy import create_engine,MetaData
# 数据库、驱动、用户、密码、ip、端口、端口号、数据库
engine=create_engine('mysql+pymysql://root:123456@localhost:3306/sqlalchemy') # 获得engine
# metadata对象
metadata=MetaData()
这一步其实可以不通过sqlalchemy,可以用pymysql,但相对来说,用sqlalchemy更好,毕竟已经是封装好的框架,不需要写sql语句。
在python_sql_java文件下创建get_sql.py文件。其中代码如下。
from sqlalchemy import Table, inspect
from settings import engine, metadata
sql_java = {
'Varchar': 'String',
}
def get_sql_word_type(table_name) -> list:
"""
:param table: 表名
:return: list
"""
inspector = inspect(engine)
columns = inspector.get_columns(table_name)
words = []
for column in columns:
word_type = type(column['type']).__name__.capitalize()
if word_type in sql_java.keys():
word_type = sql_java[word_type]
word_name = column['name']
words.append({'word_name': word_name, 'word_type': word_type})
return words
properties = get_sql_word_type('student')
for property in properties:
print(property)
打印结果如下。
也可以选择把Integer变成int类型的,或者还有其他类型的替换,差不多,具体情况具体分析。
通过pandas的read_sql_table方法,获取类型,实际上查不多,没什么区别,代码如下
import pandas as pd
from settings import engine
sql_java = {
'int64': 'Integer',
'object': 'String',
}
def get_sql_word_type(table_name):
df = pd.read_sql_table(table_name, con=engine)
field_info = []
for column in df.columns:
field_name = column
field_type = str(df[column].dtype)
if field_type in sql_java:
field_type = sql_java[field_type]
field_info.append([field_name, field_type])
return field_info
field_info = get_sql_word_type('student')
for i in field_info:
print(i)
结果如下,笔者后面将使用第一种方法
在写模板之前,先看一下IntelliJ IDEA生成的实体类Student。
public class stduent {
private Integer id;
private String name;
private Integer age;
public stduent() {
}
public stduent(Integer id, String name, Integer age) {
this.id = id;
this.name = name;
this.age = age;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "stduent{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
现在对上面代码进行模仿。
在模仿之前,先在当前目录(python_sql_java)下进行一个包,叫templates,其中放模板
在templates目录下进行一个entity.java文件,作为模板。
在python_sql_java目录新建一个mapper.py和render.py,
根据上面实体类,entity的代码如下
public class {{table_name | capitalize}}{ {#类名#}
{%- for property in properties %} {# 属性 #}
private {{property.word_type}} {{property.word_name}};
{%- endfor %}
public {{table_name | capitalize}}() { {# 无参构造 #}
}
public {{table_name | capitalize}}({# 有参构造 #}
{%- for property in properties -%}
{%- if loop.last -%}
{{property.word_type}} {{property.word_name}}
{%- else -%}
{{property.word_type}} {{property.word_name}},
{%-endif-%}
{% endfor -%}
) {
{%- for property in properties %}
this.{{property.word_name}} = {{property.word_name}};
{%- endfor %}
}
{% for property in properties %} {# get方法 #}
public {{property.word_type}} get{{property.word_name | capitalize }}() {
return {{property.word_name}};
}
{% endfor%}
{% for property in properties %} {# set方法 #}
public void set{{property.word_name | capitalize }}({{property.word_type}} {{property.word_name}}) {
this.{{property.word_name}}={{property.word_name}};
}
{% endfor%}
@Override
public String toString() { {# toString方法 #}
return "{{table_name | capitalize}}{" +
{% for property in properties %}
{%- if loop.first -%}
"{{property.word_name}}=" + {{property.word_name}} +
{% elif loop.last%}
",{{property.word_name}}=" + {{property.word_name}} +
{%- else -%}
", {{property.word_name}}='" + {{property.word_name}} + '\'' +
{%- endif -%}
{% endfor %}
'}';
}
}
render.py的代码如下
from jinja2 import FileSystemLoader,Environment
def get_mapper(table_name,properties):
loader = FileSystemLoader('./templates')
environment = Environment(loader=loader)
template = environment.get_template('entity.java')
return template.render(
table_name=table_name,
properties=properties
)
properties=[{'word_name': 'id', 'word_type': 'Integer'}, {'word_name': 'name', 'word_type': 'String'}, {'word_name': 'age', 'word_type': 'Integer'}]
java=get_mapper('student',properties)
print(java)
运行后结果如图所示
渲染后的结果还是可以的。
在mapper.py文件中写下最终的代码。
from jinja2 import Template, FileSystemLoader, Environment
from sqlalchemy import Table, inspect
from settings import engine, metadata
import os
class Entity:
def __init__(self, table_name):
self.table_name = table_name
self.sql_java = {
'Varchar': 'String',
}
def get_sql_word_type(self) -> list:
"""
:return: list
"""
inspector = inspect(engine)
columns = inspector.get_columns(self.table_name)
words = []
for column in columns:
word_type = type(column['type']).__name__.capitalize()
if word_type in self.sql_java.keys():
word_type = self.sql_java[word_type]
word_name = column['name']
words.append({'word_name': word_name, 'word_type': word_type})
return words
def get_mapper(self)->str:
words = self.get_sql_word_type()
loader = FileSystemLoader('./templates')
environment = Environment(loader=loader)
template = environment.get_template('entity.java')
return template.render(
table_name='student',
properties=words
)
def get_result(self, java_str, file_path=None):
if not file_path:
file_path = f'{self.table_name}.java'
if not os.path.exists(file_path):
open(file_path, 'w').close()
with open(file_path, 'w') as f:
f.write(java_str)
@staticmethod
def start(table_name, file_path=None):
entity = Entity(table_name)
java = entity.get_mapper()
entity.get_result(java)
Entity.start('student')
运行后在当前目录下生成student.java文件,内容和上面的java一样。
因为这个实体的属性比较少,运行没有问题,但不一定全部适用,可以会有问题,后面会继续修改。