I've looked at How to store data with dynamic number of attributes in a database, but it didn't quite answer my question. The system I am implementing is rather large, so I'll stick with the objects I am concerned about: Item and Category. An Item has a variable number of attributes depending on the Category that it belongs to. For instance, Items belonging to the "T-shirt" Category would have an attribute for size, while Items belonging to the "Car" Category would have an attribute for model. Someone logged into the system as an Admin can create new Categories with completely new attributes. What's the best way to structure this both in my Java code and in my database to make this work?
If the categories weren't dynamically created, I'd use inheritance so that I would have a TShirtItem object with its specific attributes filled in. But since it is dynamic, I'm puzzled. I saw a similar question that suggested using the Map data structure, but I am not sure how that would work...
The question I referred to at the top would suggest on the database end that I would have an Category table, and Attributes table, and relationship table linking what attributes go with what Category. This makes sense. But I'm tripped up again when thinking about the Item object. I could store the unique Category_ID in the Item table, but where would I store the attributes for each Item?
If you are constrained to use an SQL database and you need to do efficient type-aware (not merely string) queries on the attributes (like list all items with category shirt having size between 4 and 8), then the structure of the database is the hard part. The Java will follow from that. If I understand correctly, you will need something like this:
categories:
id : integer (pk)
name : varchar
attributes:
id : integer (pk)
of_category : integer (fk -> categories.id)
name : varchar
type : char(1) // 'N' for number, 'S' for string, etc.
items:
id : integer (pk)
of_category : integer (fk -> categories.id)
number_values:
value : number
of_item : integer (pk, fk -> items.id)
of_attribute : integer (pk, fk -> attributes.id)
string_values:
value : varchar
of_item : integer (pk, fk -> items.id)
of_attribute : integer (pk, fk -> attributes.id)
... additional table for each attribute type
Now for the example query:
select * from categories c, items i, attributes a, number_values v
where c.name = 'shirt' and
a.of_category = c.id and
a.name = 'size' and
a.id = v.of_attribute and
i.id = v.of_item and
v.value between 4 and 8
Hairy multiple joins are the price to be paid for runtime-defined attrbutes.
Once you have the tables right, modeling them as Java maps is straightforward. There is redundancy in the structure above: for example the character "type" field in the attribute rows. Consider triggers to make consistency checks.
Here is a simple java approach to do it. While designing large systems I always suggest to look at the bigger picture. The issue here is dynamically changing attributes. It would not be easy but yeah it is interesting.
The structure of your item class has to be like the following:
class Item{
private String itemName; // I assume all items will have a name.
private Map<ItemAttibuteName , Object> attributeMap ; // this will be a dynamic map.
public Map<ItemAttibuteName, Object> getAttributeMap(){//getter for attribute map
if( null == attributeMap)
return new HashMap<String, Object>();
return attributeMap ;
}
// you can synchronize this if needed
public void setAttribute(ItemAttibuteName name, Object value){
attributeMap.put(name, value);
}
public Object getAttribute(ItemAttibuteName name){
return attributeMap.get(name);
}
}
public enum ItemAttibuteName{
SIZE,
COLOUR
}
This approach suites your needs, further you can use a factory pattern to instantiate the Items depending on the category and make it an elegant code.
Please get back if you still have some doubts.
EDIT:
Now there will be a complicated way to get all the attributes of an element in the code while programming, because you don't have getters or setters for the attributes. Well, the approach will help if you can maintain a Set of attributes added to the Item, in the Item class or you can query for the keySet of the item class's attributeMap property.
private Set<ItemAttibuteName> attributes;
or
attributeMap.keySet();
Happy to Help
Dharam
Instead of Java it is more of design level issue. You have to figure out the way to define DB tables and that will assist you in finding the Java objects...
Lets start from category... A category may contain lot of Items, and an Item will belong only one category(Though I practical scenarios it is not a correct assumption).
So in DATABASE you have table called Category. If you wanna define attribute based on category, then have another table called Category_attribute which will hold default value for the attribute.
Now lets move to an Item. An item belongs to a Category so Item table will have category_key to have the mapping for Item n category... Item will have its attribute which are stored in ITEM_Attribute table...
Simple form of DB objects must be somewhat as mentioned below
Category
C_Id
Name
Category_Attribute
CA_ID
Name
Default_value
Category_Id(FK)
Item
I_ID
Name
C_ID(FK)
Item_attribute
IA_ID
Ca_ID(FK from category_attribute table)
I_ID(FK from item table)
Value
So whenever you create a Category, you will ask user to define associated attribute to the category.
At the time of creating the Item, you map it to category... And attributes associated to category should replicate with default value and map to item as well...
So you will be able to create Java objects easily...
I guess you can structure ur data in such a way that u define all object types in a table
and later use the below approach.
U can define tables like:
OBJECT_TYPE
OBJECTS
OBJ_PROPERTY_DEF
OBJ_PROP_VALUES
eg.
In OBJECT_TYPE
Define all object types here
object_type_code(pk) obj_name
4 car
5 t-shirt
In OBJECTS
obj_code(pk) object_type_code(fk) obj_name
1 4 BMW
2 4 Maruti
3 4 Honda
4 5 levis
5 5 polo
6 5 reebock
In OBJ_PROPERTY_DEF
Define all properties corresponding to a object in this table.
Note: Using Constants file to deifine property types would avoid another table.
Hopefully when you know the data types earlier.
obj_prop_code(pk) obj_code(fk) property_type property_name
------------- -------- ------------- -------------
12 6 8 (Integer) size
13 6 9 (String) color
14 6 10 (float) weight
15 6 11 (Boolean) is_coloured
16 6 9 (String) comments
17 3 9 (String) model
18 3 8 (Integer) version
19 3 9 (String) color
20 3 9 (String) comments
In OBJ_PROP_VALUES
U can inser the values the the above specified properties
obj_prop_val_code(pk) obj_prop_code(fk) obj_prop_value(VARCHAR) prop_value_sufix
----------------- ------------- -------------- -----------------
101 12 30 -
102 13 green -
103 14 126 gms
104 15 0 -
105 16 looks cool -
106 17 c532 -
107 18 3.22 -
108 19 black -
109 20 awesome car -
Reg Java classed:
Define all classes with corresponding properties.
eg:
ObjectTypeData
ObjectData
ObjectPropertyDefData
ObjectPropertyValuesData
In ObjectData.java
private ObjectTypeData objType;//specifies the type
private List<ObjectPropertyValueData> listProps; //will have all property values
//setter & getters and other req props
I think some attributes are major(size, color...) and some are minor(material, weight....). I will describe simplified JDL schema for this(of course exist another Stock entity which stores exact item with color, size - identified with barcode - but omitted for simpilicity). This design will help, not hard coding some part of attributes which is important for classifaing items.
Using this design Adding Data
For creating data in this design => You are creating some category with it's attribute, size type(for example Shoes => 39,40,41...(options); Tshirt => S,M,L..., Iphone 7,8,X...) => inside this category particular Article - Iphone (with this way on your E-Commerce system exist Iphone)
You adding specific Article Iphone X (size) - Color Black to your warehouse or stock with additional Category related Attributes => Material, Weight .... and system generates for you barcode. With this way you can easily specify Iphone with sizes and colors(without additional sql - with pure ORM) (even with additional attributes)
enum CategoryTarget{
F, M, K
}
entity Category{
name String,
imageUrl String,
showHomepage Boolean,
target CategoryTarget,
accOrder Integer,
isLastNode Boolean
}
entity Article{
status Integer,
name String,
code String,
rating Double,
imageUrl String,
isPopular Boolean
}
relationship ManyToOne{
Category{parent} to Category,
Article{category} to Category{article},
Category{sizeType} to SizeType
}
relationship ManyToMany{
Category{attribute} to Attribute{category}
}
entity Attribute{
name String
}
entity AttributeOption{
name String
}
relationship OneToMany{
Attribute{attributeOption} to AttributeOption{attribute}
}
entity SizeType{
name String
}
entity ArticleSize{
name String,
accOrder Integer
}
relationship OneToMany{
SizeType{articleSize} to ArticleSize{sizeType}
}
relationship ManyToMany{
Article{attributeOption} to AttributeOption{article}
}
Related
I am trying to do txt based database system. I'm stuck here now. What I want to do is enter the location of the data and then update it. I separate the data with this character. "|"
Structure like this:
ID |Name |Job |Phone Number
---+-----+--------+------------
55 |John |Plumber |555444
The id part is to find out which row it is in, and the name part is in the column.
data_Update(filename, id, "Name", "Bob Ross");
I want to do a function like this.
You could do it in following manner:
Read the file and for each line of text add a entry in your HashMap
Map<Integer, Map<String,Object>> personMap
Where key represent Id of the person, And Value represent mapping of field name to field value for the current entry.
In your db_update method, locate the person by id and update e.g.
personMap.get(Id).put(fieldname,value)
I have searched for two days and haven't seen any code that is closed to this. This is the only code in java that I seen and it's not exactly what I wanted.
conn.transact(list(list("db.fn/cas", datomic_id, "attribute you want to update", old value, new value))).get();
I have tried this code with a single value in the old value and a single value in the new value but it just stack the information instead of overlaying it.
Example: old value is chicken and new value is fish. After the transaction, it's [chicken, fish] instead of what I expected to be just [fish] and chicken will be move into archive(history).
So the question is, how do you ref the old array value and how do you give the new value an array so it'll update as what I expected to be as stated above.
I remember reading somewhere that under the hood it's just a series of values linking to one attribute. If this is the case does that mean that I have to find the datomic id of the string and change it? Also have to remove it if it's not on the new list?
FYI, these are the generic transaction functions I currently use for this kind of task (declared from Clojure, but should be fairly easy to adapt to Java if required):
[{:db/ident :bsu.fns/replace-to-many-scalars,
:db/doc "Given an entity's lookup ref, a to-many (scalar) attribute, and a list of new values,
yields a transaction that replaces the old values by new ones"
:db/id (d/tempid :db.part/user),
:db/fn (d/function
'{:lang :clojure,
:imports [],
:requires [[datomic.api :as d]],
:params [db entid attr new-vals],
:code (let [old-vals (if-let [e (d/entity db entid)] (get e attr) ())
to-remove (remove (set (seq new-vals)) old-vals)]
(concat
(for [ov to-remove] [:db/retract entid attr ov])
(for [nv new-vals] [:db/add entid attr nv]))
)}),
}
{:db/ident :bsu.fns/to-many-retract-all-but,
:db/doc "Given an entity lookup ref, a to-many (entity) attribute, and a list of lookup refs
expands to a transaction which will retract all the [origin `to-many-attr` target] relationships but those for which target is among the `to-spare-lookup-refs`"
:db/id (d/tempid :db.part/user),
:db/fn (d/function
'{:lang :clojure,
:imports [],
:requires [[datomic.api :as d]],
:params [db origin to-many-attr to-spare-lookup-refs],
:code (let [old-targets-ids (d/q '[:find [?t ...] :in $ ?to-many-attr ?origin :where [?origin ?to-many-attr ?t]]
db to-many-attr origin)
to-spare-ids (for [lr to-spare-lookup-refs] (:db/id (d/entity db lr)))
to-delete (->> old-targets-ids (remove (set to-spare-ids)))]
(for [eid to-delete] [:db/retract origin to-many-attr eid])
#_[old-targets-ids to-update-ids to-delete])}),
}]
I don't claim at all they're optimal performance or design-wise, but they've worked for me so far. HTH.
If you need a "last write wins" style consistent solution to replace all values for a particular entity for a card many attribute, your best bet is to go with a transaction function. You could take the following approach:
Get all datoms matching entity + attribute you want to retract all values for.
Generate retractions for all of them.
Create add transactions for all new values (e.g. from a passed collection)
Remove any conflicts (i.e. if you have the same EAV with both an add and a result)
Return the resulting transaction data.
I'm trying to convert a large Map> to some JavaBean. The key of map corresponds to some property of JavaBean, and the value somehow is decoded to property value. So I decided to use some util for that, but don't know what will work. There are some requirements I have to this util (or framework):
all configuration must be in separate files
should be a possibility to map dynamic quantity of keys:
there is a map:
key | value
quan | n
key_1| value_1
key_2| value_2
........ | .............
key_n| value_n
where n - is any number
and the JavaBean has a List of some beans. They have a property. value_1, value_2, ... must be mapped in this property, and in the end there must be so much beans in list, as these keys and values in map.
3.. should be a possibility to set up custom decoder for property mapping, because in most cases the value in map is a List with 1 value, so I need to get the first item of list (if it's not empty).
4.. should be a possibility run some script to execute extraordinary mappings, for example:
there is a map, that is described in 2d point.
and the JavaBean has a property of type HashMap, where value_1 is mapped to Bean1 and some analogous value from input map is mapped to Bean2.
I've tried to use smooks, but when I've started, all these requirements were not clear yet and the smooks was something new, I haven't worked with it until now. So the smooks config doesn't contain the whole business-logic (because of second req.) and looks ugly, I don't like that. I can show the most ugliest fragment for 2d point:
<jb:bean beanId="javaBean" class="com.example.JavaBean" createOnElement="map">
<jb:wiring property="someBeans" beanIdRef="someBeanItems"/>
</jb:bean>
<jb:bean beanId="someBeanItems" class="java.util.ArrayList" createOnElement="map/entry">
<jb:wiring beanIdRef="someBeanItem"/>
</jb:bean>
<jb:bean beanId="someBeanItem" class="com.example.someBeanItem" createOnElement="map/entry">
<condition>map.quan[0]>0</condition>
<jb:expression property="property1">
index = map.quan[0]-1;
value = additionalProperties.property1_List[index];
map.quan[0] = map.quan[0] - 1;
return value;
</jb:expression>
</jb:bean>
Here "property1_List" is builded before executing smooks.
Now I look for something more nice and need your help: maybe you know how to make that better using smooks? Or what another frameworks for mapping can you recommend for my issue?
I have a field called phone number which has values such as 0833888999 that I want to format as following 0833 888 999.
The answer that Rachana offered blew is true for few countries but not all countries.
Therefore,
I am using this Google library to format contact numbers from different countries; however, the problem is that after persisting the contact numbers in database I can not search for them on database, for example the library would format contact numbers of different countries in different format, for example add space or "-" between them that makes hibernate unable to find them.
+18182223333 >>> +1 818-222-3333
+441135558888 >>> +44 113 555 8888
Hibernate
.add(Restrictions.ilike("user.phone","+18182223333");
Try this
<s:property value="getText('{0,number,0### ### ###}',{phone})"/>
Where,phone=0833888999
Hope this will help you also see Using Struts2 Tags to Formatting Numbers
you will get clear idea about number formatting
I think you should keep the raw phone number (e.g. 0833888999) in the database and it's the View responsibility to format it accordingly.
You can have a separate table "country_phone_format" holding a country_id and a phone_format and you could fetch the phone_format as a #ManyToOne entity so that you have both the raw data and the format to properly display it into the View.
The PhoneFormat could be easily cached with the second level cache, as they should be rarely modified.
Just as Vlad mentions, you should keep the raw phone number (e.g. 0833888999) in the database (or save it with the country and area code if you prefer) and leave th responsibility to format it accordingly to the View.
You can use Type Conversion to convert to/from the format you desire - and take advantage of the google library you mention. Something like the following can get you started (abbreviated just to get the gist of it):
public class MyConverter extends StrutsTypeConverter {
public Object convertFromString(Map context, String[] values, Class toClass) {
String phoneNr = values[0]
MyPhoneObject phone = googleLibrary.doYourMagic(phoneNr);
return phone;
}
public String convertToString(Map context, Object o) {
googleLibrary.parseString( ((MyPhoneObject)o).rawPhoneNr() );
}
}
Don't forget to register the converter in xwork-conversion.properties
my.PhoneObject=path.to.MyConverter
I need to make search for an object by specific attributes. I have a table item_attribute:
item_attribute item_attribute_type_fk item_fk value_text
1 1 1 bbbb
2 2 1 450-240-310
3 3 1 800x250
4 4 1 2,2
5 1 2 HBO
etc
I've tried this:
SELECT item,name,sale_price,producer,producer_code,store_price,I.type_name
FROM ((item INNER JOIN unit_type ON unit_type_fk=unit_type)
INNER JOIN item_store ON item=item_fk)AS I
INNER JOIN item_attribute AS A ON I.item=A.item_fk
WHERE store_price BETWEEN 200.0 AND 300.0
AND (UPPER(value_text) LIKE UPPER('%b%') AND item_attribute_type_fk=1)
AND (UPPER(value_text) LIKE UPPER('%2%') AND item_attribute_type_fk=4)
GROUP BY item,I.type_name ORDER BY item
Result should be row, where item_fk = 1, but in reality select returns zero rows. If I change AND to OR between attributes that will result in several rows, which is not exactly what I want, because search need to return only that object, that has all attributes, that user typed in in search field. How should I change code, that it will look for a value only through attributes with specific item_attribute_type_fk and return row with object, that has all that attributes?
You appear to be using an EAV-like schema.
To query multiple values in EAV you have to join the same table multiple times. e.g.
SELECT item,name,sale_price,producer,producer_code,store_price,I.type_name
FROM
item
INNER JOIN unit_type ON unit_type_fk=unit_type
INNER JOIN item_store ON item=item_fk)AS I
INNER JOIN item_attribute AS a1 ON I.item = a1.item_fk
INNER JOIN item_attribute AS a2 ON I.item = a2.item_fk -- note second join
WHERE store_price BETWEEN 200.0 AND 300.0
AND (UPPER(a1.value_text) LIKE UPPER('%b%') AND a1.item_attribute_type_fk=1)
AND (UPPER(a2.value_text) LIKE UPPER('%2%') AND a2.item_attribute_type_fk=4)
GROUP BY item, I.type_name
ORDER BY item
Consider transitioning to using json or hstore for your attributes. It's lots less painful to query.